Pin instructions where the model actually reads them - #155
Merged
Conversation
Rules stated in CLAUDE.md sit thousands of tokens behind the live turn and get read as background. `@pxpipe pin <text>` moves them to the end of the request, immediately before generation. Pins are re-derived from the transcript each request, so the block is a pure function of the conversation and a resumed session rebuilds it identically. The block lands after the cached prefix, so it never invalidates the cache, and its characters are tracked as pinChars rather than booked as savings.
CodeQL flagged all four PIN_CMD_RE call sites: the pattern ran on every line of every prompt, and its leading, lazy, and trailing groups all matched whitespace, so a line of many tabs made the engine retry every way to split the run before failing. Before: /^[ \t]*@pxpipe[ \t]+(pin|unpin)\b[ \t]*(.*?)[ \t]*$/ After: /^@pxpipe[ \t]+(pin|unpin)\b(.*)$/ with .trim() in JS Also skip the strip when the tail cannot take the pin block, so a body that cannot be appended to keeps its rules instead of losing them.
Second CodeQL alert, same rule, different pattern: the lazy body under a global replace rescans to the end of the string for every `<system-reminder>` that never closes, so a message carrying many of them costs O(n²). Prompt text reaches this on every request. Before: raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '') After: indexOf scan; an unterminated open ends the scan and the rest stays, since it is text the user can see.
teamchong
added a commit
that referenced
this pull request
Jul 27, 2026
foldPins only scanned the leading <system-reminder> run of message 0, where Claude Code inlines CLAUDE.md. OpenCode inlines AGENTS.md into the system prompt instead, unwrapped and unlabelled, so #155 folded nothing, stripped nothing, and raised no error under OpenCode. Scan the system prompt too, at the durable 'file' tier. No reminder gate there: the user cannot type into the system prompt, so every line in it was placed by the harness, and a pin command found there came from a file. Strip mirrors stripFromMessage, cache_control handoff included, since system blocks carry the breakpoint that ends the cacheable prefix. It must run before the system text is collected for imaging, or the stripped lines get baked into the rendered slab. Messages API only. foldPins still has exactly one call site, so Codex and Gemini remain no-ops.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Opus 5 stops following the instructions you gave it. It overthinks, wanders
into files nobody asked about, and answers a question adjacent to the one you
typed. The rules are in CLAUDE.md, which the model read thousands of tokens
ago and now treats as background. Repeating the rule in the current turn fixes
it every time, which says the problem is distance, not comprehension.
Mechanism
pxpipe reads
@pxpipe pin <text>out of the user message, strips the commandline from the copy sent upstream, and re-emits the active pins as a
<system-reminder>block appended to the last user message. Every request nowends with the rules, immediately before the model starts generating.
Pins are re-derived from the transcript on each request, so the block is a
pure function of the conversation. No server-side state and a resumed session
reconstructs the same block.
Cost: the pin block is not cacheable
It sits after the cached prefix, on purpose. Moving it earlier would put the
rules back in background position and invalidate the prompt cache on every
edit. So the block is re-sent uncached each turn and billed as fresh input.
The dashboard already accounts for it: those characters are counted as
pinChars, not booked as savings, so the savings number stays honest.Keep pins short. A few lines is the intended size; a pasted style guide is paid
for on every request.
Usage
Two tiers, same syntax:
@pxpipe pinline in CLAUDE.md / AGENTS.md is durableCommand-only turns are answered by the proxy and never reach the model:
@pxpipe pinreports,@pxpipe unpin <n>andunpin allremove.A turn counts as command-only by what the user typed. Harness-injected
<system-reminder>blocks are stripped first, otherwise Claude Code's ownnotices make a bare
@pxpipe unpinlook like real work and bill it as a modelturn.