Pin instructions that survive compaction, /clear, and restarts. /pin saves a rule for this project and every prompt carries the pinned list as context Claude…

Pin instructions that survive compaction, /clear, and restarts. "Don't touch the migrations," said an hour ago, is gone after compaction and forgotten in the next session. /pin saves it for this project, and every prompt you send carries the pinned list as context Claude reads but the transcript doesn't show.
/plugin marketplace add MDmubarak786/claude-mods
/plugin install pins@modhub
Try it for one session without installing:
claude --plugin-dir ./mods/pins
| Command | What it does |
|---|---|
/pin don't touch the migrations | Pin an instruction for this project. Up to 25. |
/pin | List the pins. |
/pin rm 2 | Remove pin 2. |
/pin clear | Remove them all. |
What Claude reads after each of your prompts, unchanged from prompt to prompt so it doesn't disturb the prompt cache:
Pinned instructions from the user for this project. They apply to every turn, including after compaction:
1. don't touch the migrations
2. run the tests before saying done
At session start, a dim line says how many pins the project has.
From claude plugin validate ./mods/pins:
hooks: session.start, command.run{command=pin}, prompt.submit
calls: $.command.register, $.session.root (via key), $.store.delete, $.store.get (via load), $.store.set, $.ui.log
prompt.submit adds the list as context after your prompt. Your own message is never rewritten.$.store holds the pins, keyed by project root. No files, processes, or network.claude plugin validate --strict and claude plugin test pass. /pin answered from a live claude -p session.fence; for banned words use style-cop.MIT, see the repository root.
hooks/register.ts 72 lines1// pins: instructions that survive compaction, /clear, and restarts.
2//
3// /pin don't touch the migrations pin an instruction for this project
4// /pin list the pins
5// /pin rm 2 remove pin 2
6// /pin clear remove them all
7//
8// Every prompt carries the pinned list as context that Claude reads and the
9// transcript doesn't show. Pins are saved per project root, so they're there
10// after compaction, after /clear, and in the next session.
11
12const MAX_PINS = 25
13
14async function key($): Promise<string> {
15 return 'pins:' + (await $.session.root())
16}
17
18async function load($): Promise<string[]> {
19 try {
20 const saved = await $.store.get(await key($))
21 return Array.isArray(saved) ? saved.filter((p) => typeof p === 'string') : []
22 } catch {
23 return []
24 }
25}
26
27function list(pins: string[]): string {
28 return pins.map((p, i) => (i + 1) + '. ' + p).join('\n')
29}
30
31export function register(on) {
32 on('session.start', async ($, e, next) => {
33 try {
34 await $.command.register({ name: 'pin', description: 'Pin an instruction that survives compaction and new sessions', argumentHint: '[text | rm N | clear]', immediate: true })
35 } catch (error) {
36 $.ui.log('could not register /pin: ' + error)
37 }
38 const pins = await load($)
39 if (pins.length) $.ui.log(pins.length + ' pinned instruction(s) for this project. /pin lists them.')
40 return next(e)
41 })
42
43 on('command.run', { command: 'pin' }, async ($, e) => {
44 const args = e.args.trim()
45 const pins = await load($)
46 if (!args) return { text: pins.length ? 'Pinned for this project:\n' + list(pins) : 'No pins. /pin <text> adds one.' }
47 if (args === 'clear') {
48 await $.store.delete(await key($))
49 return { text: 'Removed ' + pins.length + ' pin(s).' }
50 }
51 const rm = /^rm\s+(\d+)$/.exec(args)
52 if (rm) {
53 const n = Number(rm[1])
54 if (n < 1 || n > pins.length) return { text: 'No pin ' + n + '. ' + (pins.length ? '/pin lists them.' : 'There are no pins.') }
55 const [gone] = pins.splice(n - 1, 1)
56 await $.store.set(await key($), pins)
57 return { text: 'Removed pin ' + n + ': ' + gone }
58 }
59 if (pins.length >= MAX_PINS) return { text: 'Already ' + MAX_PINS + ' pins. Remove one first.' }
60 pins.push(args)
61 await $.store.set(await key($), pins)
62 return { text: 'Pinned (' + pins.length + '): ' + args }
63 }).catch(async () => ({ text: 'pins: the command failed, so nothing changed.' }))
64
65 on('prompt.submit', async ($, e, next) => {
66 const pins = await load($)
67 if (!pins.length) return next(e)
68 const text = 'Pinned instructions from the user for this project. They apply to every turn, including after compaction:\n' + list(pins)
69 return next({ ...e, context: [...(e.context ?? []), text] })
70 }).catch(async ($, e, next) => next(e))
71}
72