The short version
- Luminair's Claude lane runs on the Claude Agent SDK. It added its own instructions through an option called
appendSystemPrompt. - On 27 September 2026 we found that SDK 0.3.281 never reads that option, and that leaving out
systemPromptmeans an empty system prompt, not Claude Code's. - So app rules, your abilities and Solace verifier's
VERDICTanswer format reached no model. A verifier reply with no VERDICT line is scored unknown. - Every SDK query already went through one options function. It now folds the old option into the preset shape the SDK reads, and a test fails if that ever changes.
Found by reading the SDK.
The fix landed on 27 September 2026 in a commit co-authored by Claude. The work behind it did not guess. It read the SDK's own bundled source to see which option keys the normalizer actually takes, then ran a live probe: the same rule passed one way was obeyed, and passed the other way was ignored. The module's header records both.
This post was drafted by another session reading that module, its tests, the call sites in the main process and the git history, with every outside claim checked against the page it links to.
The default changed a year earlier.
On 29 September 2025 Anthropic renamed the Claude Code SDK: “To reflect this broader vision, we're renaming the Claude Code SDK to the Claude Agent SDK.” The Claude Sonnet 4.5 announcement the same day described it as “the same infrastructure that powers Claude Code”.
The rename came with a behaviour change that is easy to miss. The migration guide has a section for it, and its first line is: “The SDK no longer uses Claude Code's system prompt by default.” The docs on modifying system prompts spell out what you get instead: a minimal prompt that “covers tool calling but omits the rest of the claude_code preset's content”. To get Claude Code's behaviour you set the preset, “optionally with append to add your own instructions on the end.”
That append lives inside systemPrompt. Luminair was passing appendSystemPrompt at the top level of the options, a name that reads naturally and that nothing complained about.
More than a few rules.
Think of the system prompt as the briefing a new colleague reads before their first task: house rules, what tools they may use, how to hand work back. Luminair assembles a long one for every Claude turn. On the desktop lane alone, the options are built with more than a dozen additions stacked onto appendSystemPrompt, one line at a time.
Desktop Claude turn
you type in the app- Question-block format for tappable questions
- Session origin marker
- Your session abilities
- Journal memory, bundle protection, evidence and token rules
- Workflow phase prompt
- Handoff contract and session memory
- Turn contract and goal gate
- AGENTS.md, broker contract, cross-model bridge
Phone relay turn
you send from the phone- Handoff contract and session memory
- Turn contract and goal gate
- AGENTS.md and broker contract
- Cross-model bridge
- Connector awareness (when allowed)
Solace
background checks- Feature verifier: the VERDICT format
- Feature verifier: treat the diff as data
- Runtime probe: the PROBE format
- Daily report instructions
appendSystemPrompt in the 1.0.229 source, the last build before the fix. Struck through because, with SDK 0.3.281, none of it reached the model. Bold rows are the ones whose loss has a visible symptom.Nothing here failed loudly. The model still answered, still used tools and still wrote code. It just did so without the briefing, and without Claude Code's own preset either, since a missing systemPrompt gave it the minimal default. A turn that ignores your abilities, or asks a blocking question as plain prose instead of a tappable card, looks like the model having an off day.
It could only say unknown.
One caller shows the damage most clearly. Solace can check a finished feature with an independent verifier: a separate, read-only Claude query that traces the feature through the code and reports whether it really works. Its instructions end with the answer format: reply with exactly one line, “VERDICT: PASS” or “VERDICT: FAIL”, then a one-sentence reason.
The app then reads the reply with a regular expression, taking the last verdict line so a reply that narrates both outcomes cannot fool it:
let mm = null; for (const m of out.matchAll(/VERDICT:\s*(PASS|FAIL)/gi)) mm = m; const verdict = mm ? mm[1].toLowerCase() : 'unknown';
With the instructions dropped, the verifier model was never told about the format, so it had no reason to write a VERDICT line. And a reply without one falls through to 'unknown'. The fix commit's module header names the verifier's format among the things that “reached no model”.
There is a second, quieter lesson in the same caller. The verifier's instructions also tell it to treat the diff as data and ignore any instruction or verdict embedded in it. That line was lost too. What held was the part that does not depend on the prompt: the verifier's options come from a read-only helper that limits its tools and denies all writes before any permission check. A code comment puts the reason plainly: the verifier “must be physically unable to act on an injected instruction”. Prompts are advice. Permissions are walls.
One door for every query.
The fix was small because of an earlier decision. Every Agent SDK query in the main process already passed its options through one function, secureSdkOptions, which exists to guard spawn paths and tool servers. There are eight query call sites, and all eight go through it. So one line there fixed all of them:
// appendSystemPrompt is folded into systemPrompt.append: the SDK ignores the top-level // option and would run with an empty system prompt (lib/core/sdk-system-prompt.js). const options = sdkSystemPrompt.normalize({ ...(input || {}) });
The new module, desktop/lib/core/sdk-system-prompt.js, is 39 lines. It removes the dead key and puts its text where the SDK looks, whatever shape the caller already used:
| Caller passed | SDK now receives |
|---|---|
only appendSystemPrompt: "R" | systemPrompt: { type: "preset", preset: "claude_code", append: "R" } |
a string systemPrompt + addition | one string, the two joined by a blank line |
a preset with its own append | the same preset, both appends joined |
a custom prompt | the custom prompt with the addition on the end |
| an empty addition | the dead key removed, nothing else changed |
secureSdkOptions calls normalize. The sixth is a canary described below.The canary is the part we like best. It opens the bundled SDK's source, finds the normalizer's list of option keys and asserts that appendSystemPrompt is still not among them. Its name is an instruction to a future maintainer: “drop this module when it stops”. If an SDK update starts reading the old option again, that test is where we will find out.
We measured what we meant to send.
Luminair has a context meter: type //context and it lists the blocks the last turn carried. For Claude turns it records a block called “System additions”. Before the fix, that block was filled from appendSystemPrompt itself, the very value the SDK was dropping. So the meter faithfully showed our rules in every turn, as if they had been sent.
Nothing about that was dishonest; it measured the options we handed over. But it measured one step too early. The fix changed the capture to read the normalized options, the shape that actually crosses into the SDK. The general rule we took from it: a meter should read as close to the wire as it can, or say where it stops looking.
systemPrompt, either a string of your own or the claude_code preset with append. And run one probe after every SDK upgrade: a rule the model will obviously follow or ignore, sent the way your app sends it.What we don't know.
What this post does not claim
- That older SDK versions ignored the option too. Luminair moved from 0.3.220 to 0.3.281 on 23 September 2026, and we did not test the older one.
- How many verifier results came back unknown because of this. The mechanism is clear from the code; we have not recounted past runs.
- How much answer quality changed after the fix. We confirmed the rules now arrive; we did not measure what they improve.
- Anything about the other engines. Codex, Gemini and the rest receive Luminair's rules through their own command-line preambles, not this SDK option.
For the wider pattern, when a harness makes a good model look worse, read Your model didn't get dumber.
Sources
- Claude blog · 29 September 2025Building agents with the Claude Agent SDK
- Anthropic · 29 September 2025Introducing Claude Sonnet 4.5
- Claude docs · Agent SDKMigrate to Claude Agent SDK
- Claude docs · Agent SDKModifying system prompts
See what your turn carried
Type //context in any session to see the blocks Luminair sent with it.