fix(agent): append system_prompt to the Claude Code preset instead of replacing - #92
fix(agent): append system_prompt to the Claude Code preset instead of replacing#92Mihaiii wants to merge 6 commits into
Conversation
… replacing A plain-string ClaudeAgentOptions.system_prompt replaces Claude Code's entire default system prompt. Every experiment that sets even a one-line system_prompt silently strips the harness's behavioral guidance — observed in skills nightly runs as zero parallel tool calls (the batching instruction lives in the default prompt), heavy narration, and raw cat/sed over Read/Grep. Wrap the configured prompt in the SDK's claude_code preset with append so the default prompt survives. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CodexAgent silently dropped config.system_prompt; forward it as developer_instructions (injected on top of the Codex base prompt) to match the append semantics of Claude Code (claude_code preset) and Antigravity (TemplatedSystemInstructions, which already appended). Also document the ripple effects of append-only system_prompt: - agent_judge: the reviewer prompt is now layered after the full Claude Code preset instead of replacing it (accepted trade-off, noted in code) - BaseAgentConfig.system_prompt description states per-agent semantics - docs: fix the stale "Replaces the default" claim in CLAUDE_CODE.md, add a System prompt row to CODEX.md, document Antigravity's append shorthand Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Blockers from the PR #92 review: - system_prompt unset no longer loses the preset: the SDK maps None to --system-prompt "" (an explicit EMPTY prompt), so _build_options now always sends the claude_code preset — bare (CLI default prompt) when unset, with `append` when configured. This fixes the common no- system_prompt case, which previously ran without Claude Code's default behavioral guidance. - agent_judge no longer inherits the coding-agent preset: new ClaudeCodeAgentConfig.system_prompt_mode ("append" default / "replace"), forced to "replace" in _build_agent_config next to the existing security floors, so the judge prompt stays its entire identity and verdicts can't shift with the preset. Pinned by test. - exclude_dynamic_sections=True on the preset keeps the system prompt static across runs (no per-run tempdir path baked in); the SDK re-injects the stripped sections into the first user message. - Transport-level tests: captured options are rendered through SubprocessCLITransport._build_command() asserting the exact flag emitted (--append-system-prompt vs --system-prompt vs none) — the surface the original bug lived on. Also pins system_prompt: "" and the renamed unset-case test (the old name asserted a false SDK contract). - BaseAgentConfig.system_prompt description is agent-neutral again; the claude-specific mechanism lives on ClaudeCodeAgentConfig + docs/agents/. MIGRATION NOTE: system_prompt semantics on claude-code changed from replace to append, and runs WITHOUT system_prompt now get the real Claude Code default prompt instead of an empty one. Scores are comparable only within one semantics regime — re-baseline judged tasks (e.g. tasks/python_cli_simulated_judged/echo_simulated_judged.yaml, whose prompt was written against replace semantics) and pin runs to the CLI version recorded in environment_info.claude_code_cli. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Trend dashboards need to segment runs by system-prompt regime instead of silently pooling pre-/post-append-semantics scores (PR #92 review, cross-run comparability blocker). Each built-in agent now emits system_prompt_semantics via get_environment_info(), merged into run.json: - claude-code: the resolved system_prompt_mode ("append" / "replace") - codex: "append" (developer_instructions; previously the field was silently dropped, so codex runs also cross a semantics boundary here) - antigravity: "append" (unchanged behavior, emitted for uniformity) Runs without the marker predate the change and used replace-on-set / empty-on-unset (claude-code) or dropped (codex) semantics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@akshaylive The review is addressed now, please have a second look. Antigravity doesn't need any change because it already is on append mode by default, not replace. |
…reports, replace validator
Blockers:
- UserSimulator now sets system_prompt_mode="replace" so the roleplay
persona is the simulator's ENTIRE system prompt (the claude_code
coding-agent preset no longer prefixes it on dialog-mode runs), and
SubAgentRunner fail-louds on any identity-prompt config left in
append mode (mirrors the setting_sources guard).
- reports.collect_agent_settings_rows understands the persisted
SystemPromptPreset dict: renders the appended prompt text (never the
dict repr), omits the row for a bare preset, and surfaces a
"System Prompt Mode: replace" row for plain strings. REPORT_SCHEMA.md
documents the sdk_options.system_prompt type change and the
environment_info.system_prompt_semantics segmentation rule.
Non-blocking:
- ClaudeCodeAgentConfig rejects system_prompt_mode="replace" with no
system_prompt/system_prompt_file; _effective_prompt_mode() is the
single source of truth for both the options builder and the
system_prompt_semantics marker, so run.json can never disagree with
the wire.
- Softened the false "never a replacement" prose on
BaseAgentConfig.system_prompt.
Nits:
- Antigravity forwards system_prompt verbatim ("" no longer dropped by
`or None`), matching Claude Code / Codex `is not None` semantics.
- Corrected the stale Codex get_environment_info docstring and the
"always kept" row in docs/agents/CLAUDE_CODE.md; AB_EXPERIMENTS.md
lists system_prompt_mode as a variant lever.
- Typed the test helpers (_transport_command / _capture_sdk_options),
asserted the whole preset dict in the empty-string test, narrowed
_transport_command's docstring to argv-pinning only.
Tests: simulator replace-mode assertion, SubAgentRunner guard,
replace-with-unset-prompt validator case, environment_info merge
survival at the orchestrator seam, and report tests fed from a real
dump_dataclass(ClaudeAgentOptions).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@uipreliga @akshaylive Addressed. Please have another look. |
akshaylive
left a comment
There was a problem hiding this comment.
Re-review: fix(agent): append system_prompt to the Claude Code preset instead of replacing
PR #92 by @Mihaiii · fix/sys-prompt-append → main · OPEN · reviewed against 0e444f0 (previous review was against f066834)
This is a strong response to the last round — every blocker is genuinely closed, and several were verified on the wire rather than by reading the diff. The judge seam in particular is now defended at three independent layers (forced assignment after model_copy, a fail-loud SubAgentRunner guard, and system_prompt already being in _FRAMEWORK_OWNED_SDK_FIELDS), and a hostile YAML override was confirmed unable to win. The unset path is fixed properly — the preset now goes out unconditionally, so --system-prompt "" is gone — and _transport_command() pins the argv contract rather than the TypedDict shape, which is the right regression surface for this bug class. exclude_dynamic_sections=True is a real reproducibility win (verified honored through the SDK's initialize control request, not inert), and Codex no longer silently drops the field. Two things still block: a documented config combination (system_prompt_mode: replace + system_prompt_file) hard-crashes at task load, and the migration surface for the semantics flip is missing. Overall 9.2 / 10 (up from 8.9), weakest axis Evaluation Harness Quality at 8.0 / 10 (up from 5.5).
Summary
| Axis | Score | 🔴 | 🟠 | 🟡 | 🔵 | Top Issue |
|---|---|---|---|---|---|---|
| 1. Code Quality & Style | 8.9 / 10 | 0 | 0 | 2 | 1 | _effective_prompt_mode returns a mode string, forcing an -O-stripped assert re-check |
| 2. Type Safety | 9.9 / 10 | 0 | 0 | 0 | 1 | system_prompt_mode passed through an untyped **kwargs seam in the simulator |
| 3. Test Health | 9.3 / 10 | 0 | 0 | 1 | 2 | Antigravity's changed "" forwarding is untested and has no CI extra |
| 4. Security | 10.0 / 10 | 0 | 0 | 0 | 0 | No findings — prior judge trust-boundary finding verified closed on the wire |
| 5. Architecture & Design | 9.4 / 10 | 0 | 0 | 1 | 1 | system_prompt_semantics emitted by only 3 agents, but documented as a global contract |
| 6. Error Handling & Resilience | 8.9 / 10 | 0 | 1 | 0 | 1 | replace + system_prompt_file hard-fails at task load |
| 7. API Surface & Maintainability | 9.5 / 10 | 0 | 0 | 1 | 0 | CE030 doc-parity still doesn't cover agent configs |
| 8. Evaluation Harness Quality | 8.0 / 10 | 0 | 1 | 2 | 0 | Affected task not re-baselined; no migration note or BREAKING CHANGE: footer |
Overall Score: 9.2 / 10 · Weakest Axis: Evaluation Harness Quality at 8.0 / 10
Totals: 🔴 0 · 🟠 2 · 🟡 7 · 🔵 6 across 8 axes reviewed. (Previous: 🔴 1 · 🟠 4 · 🟡 2 · 🔵 5.)
Prior blockers — all closed
| Prior finding | Status |
|---|---|
agent_judge ran with the coding-agent preset prepended (🔴) |
RESOLVED — verified on the wire against a hostile YAML override |
test_system_prompt_none_leaves_sdk_default pinned a false contract (🟠) |
RESOLVED — preset sent unconditionally; argv-level tests added |
| Cross-run comparability broke with no marker (🟠) | PARTIALLY — marker added; task not re-baselined, no migration note |
| Shared base field had three contradictory semantics (🟠) | RESOLVED — codex implemented, antigravity verified against its SDK |
docs/agents/CLAUDE_CODE.md:102 contradicted the code (🟠) |
RESOLVED — all three agent pages now correct and consistent |
No transport-level test (🟡) · exclude_dynamic_sections unset (🟡) |
RESOLVED — both |
| Empty-string case (🔵) · non-root import comment (🔵) | RESOLVED — both |
Blockers
-
system_prompt_mode: replace+system_prompt_file:hard-fails at task load — the exact combination the new docs and the validator docstring advertise as supported.check_replace_mode_has_promptaccepts the pair at construction, butresolve_agent_system_prompt(src/coder_eval/orchestration/task_loader.py:239-241) clearssystem_prompt_file = Nonebefore assigningsystem_prompt = content, deliberately, to dodge the mutual-exclusivity validator. BecauseBaseAgentConfigsetsvalidate_assignment=True(models/agent_config.py:123), that first assignment re-runs the new after-validator against the intermediate state(mode="replace", system_prompt=None, system_prompt_file=None)and raises — with an error naming the very field the user did set, so it reads as self-contradictory. The experiment path hits it too (experiment.py:529). Reproduced end-to-end:constructed OK: p.md replace RESOLVE FAILED: ValidationError — system_prompt_mode='replace' requires system_prompt (or system_prompt_file) to be setSuggested fix: set both fields in one batched update (
model_copy(update={"system_prompt": content, "system_prompt_file": None})) so no half-updated state is ever validated — note a naive reorder doesn't work either, it just trips the mutual-exclusivity validator instead. (src/coder_eval/models/agent_config.py:258-273) -
The one test covering that combination asserts the wrong half of the flow.
test_system_prompt_mode_replace_requires_prompt(tests/test_agent.py:487-496) only checks thatparse_agent_config(system_prompt_file=..., system_prompt_mode="replace")constructs; its comment claims "task_loader inlines it into system_prompt at resolution time" but it never calls the loader. That's structurally the same shape as last round'stest_system_prompt_none_leaves_sdk_default— a green test standing in front of a broken path. A test that goes throughload_task(orresolve_agent_system_promptdirectly) would have caught blocker 1. -
The semantics flip ships with no migration surface.
tasks/python_cli_simulated_judged/echo_simulated_judged.yaml:19is worded specifically for replace semantics ("reply with that exact string verbatim and nothing else — no preamble, no commentary… Ignore any project context") and isllm_judge-graded, so its baseline moves — but it wasn't re-baselined, reworded, or pinned tosystem_prompt_mode: replace. Codex users are affected more sharply: the field went from silently dropped to injected asdeveloper_instructions, andsystem_prompt_modedoesn't exist onCodexAgentConfig(confirmedextra_forbidden), so there's no way back short of deleting the field — yet the only user-facing trace is one cell in a comparison table 200 lines intoCODEX.md, and its "Known Limitations" section (where a reader would have learned the field was ignored) is untouched. No commit carries aBREAKING CHANGE:footer, so semantic-release won't put this inCHANGELOG.md. Thesystem_prompt_semanticsmarker lets a dashboard segment the discontinuity, but does nothing for anyone comparing against a stored baseline. (src/coder_eval/agents/claude_code_agent.py:1180-1199,docs/agents/CODEX.md:216)
Non-blocking, but please consider before merge
Correctness gaps
system_prompt: ""withsystem_prompt_mode: replacepasses validation and sends an empty string as the entire system prompt — precisely the degraded regime this PR exists to eliminate. The validator checksis None, and_effective_prompt_mode()testsis not None, so blank slips through both. An empty or whitespace-onlysystem_prompt_filereaches the same state (task_loaderdoescontent = ….strip()). Verified:parse_agent_config(type=CLAUDE_CODE, system_prompt="", system_prompt_mode="replace")constructs cleanly. Normalizing a blank resolved prompt toNoneinresolve_agent_system_promptwould fix this and the Antigravity""case at one seam. (src/coder_eval/models/agent_config.py:258-272)- The
SubAgentRunnerguard only fires whensystem_prompt is not None, so a sub-agent that omits the prompt still gets the full coding-agent preset — the same failure, reached via the omission branch. Not reachable in-tree today, but the guard is written as a contract for callers who "own their config" and doesn't enforce what it states. The invariant is "a sub-agent must have asystem_promptand be inreplacemode". (src/coder_eval/evaluation/sub_agent.py:90-100) _effective_prompt_mode's fail-open (replace+ no prompt →append) degrades silently. Fail-open is the right posture here, but alog.warningwould make a silently-downgraded judge visible intask.lograther than only inferable fromrun.json. (src/coder_eval/agents/claude_code_agent.py:1243-1255)
Contract & schema
system_prompt_semanticsis emitted by exactly the three agents this PR touched.agents/noop_agent.pyand every out-of-tree SPI agent (e.g. thecoder_eval_uipathDelegate agent) emit nothing, whiledocs/REPORT_SCHEMA.md:140-144documents absence as "pre-append regime" — so a dashboard following the documented rule permanently mis-buckets brand-new plugin runs into the pre-migration cohort. GivingAgent.get_environment_info()a base default (or stamping it in the orchestrator) makes the documented rule true and removes the two hardcoded literals at the same time. Also worth noting the pre-marker semantics differ per agent: codex dropped the field, antigravity already appended — so those runs are actually comparable and the doc over-warns for them.docs/AB_EXPERIMENTS.md:133-137listssystem_prompt_modeas an A/B lever ("append-vs-replace arms") alongsidemodelandallowed_tools, with no caveat — whileCLAUDE_CODE.mdsays it's "rarely needed in tasks". Areplacearm sends no default prompt at all, so anyone A/B-testing append vs replace will attribute the delta to their prompt when it's dominated by the missing behavioral guidance. Either drop it from the list or annotate what the arm actually measures.
Testing
- Antigravity is the one agent whose behavior actually changed (
or Nonedropped, so""is now forwarded assystem_instructions) and it's the one with no coverage: the new test only asserts theenvironment_infomarker, nothing referencessystem_instructions, and CI never installs theantigravityextra (grep -c antigravity .github/workflows/pr-checks.yml= 0). Extracting a pure_build_local_agent_kwargs()(mirroring Codex's_build_thread_options) would make the forwarding assertable without the SDK. Good news on the Codex side: CI does runuv sync --extra codex, so those tests genuinely execute. - Codex's
developer_instructionstests compare a literal against the dict the line under test just built, so a misspelled kwarg wouldn't be caught statically (_build_thread_optionsreturnsdict[str, Any]splatted intothread_start). The name is right today (openai_codex/api.py:139), so this is drift exposure — aninspect.signatureassertion under the existingopenai_codexskip would close it. _effective_prompt_mode's fail-open branch and_unwrap_system_prompt's(None, None)fallthrough are both untested.
Design / readability
_effective_prompt_mode()returns a mode string, which forces its only caller to re-establish the invariant the helper just checked, via a bareassertthatpython -Ostrips. There are now three layers guarding one state. Having it return the resolved value (str | SystemPromptPreset) and deriving the marker from that removes the assert and the re-derivation while keeping the SSOT property. Worth notingsub_agent.py:82-84in this same PR states the opposite convention explicitly ("Notassertbecause the check must survivepython -O") — verified the assert isn't a live hole, but the inconsistency invites a future reader to treat it as a real guard. (src/coder_eval/agents/claude_code_agent.py:1191-1198)reports._unwrap_system_promptre-derives the regime by sniffing the wire value's Python type rather than reading thesystem_prompt_semanticsmarker this PR adds, and the "System Prompt Mode" row therefore appears only in replace mode — so an append run is visually indistinguishable from a pre-PR run. Reading the marker instead (and emitting the row unconditionally) would be both more correct and simpler. While there,-> tuple[str | None, Literal["replace"] | None]would tighten the return type. (src/coder_eval/reports.py:60-74)- CE030 doc-parity still doesn't cover agent configs (
tests/lint/doc_schema_parity.py:45-48tracks onlyTaskDefinition/RunLimits/Dataset/SimulationConfig, and its header explicitly excludes nested models). Last round's doc drift happened on exactly this untracked surface, and this PR adds another user-facing field plus a hand-written doc row to it.
Nits
- The
_build_optionscomment grew from 4 to 10 lines; the first 8 carry genuinely non-obvious SDK behavior, but the last two restate what_effective_prompt_mode's docstring and theField(description=…)already say. (src/coder_eval/agents/claude_code_agent.py:1180-1190) - The "a sub-agent's prompt is its entire identity" rationale is now written out in six places; only
agent_judge.py:271cross-references instead of restating. Keeping the full version onSubAgentRunner's guard (the enforcement point) and pointing at it elsewhere would be tighter. system_prompt_mode="replace"reaches the simulator throughparse_agent_config(**kwargs: Any), so pyright checks nothing — a typo would only surface at construction viaextra="forbid". The code already assertsisinstance(agent_config, ClaudeCodeAgentConfig)two lines later, so constructing it directly would be statically checked. (src/coder_eval/simulation/user_simulator.py:205-220)- The
system_prompt_semanticsvalue is a bare literal in three places with no shared type; a typo like"appended"type-checks and silently splits dashboard buckets. ASystemPromptSemantics = Literal["append", "replace"]alias next to the config field would fix it. docs/agents/ANTIGRAVITY.mddoesn't mention the empty-string case despite that being the behavior this PR changed there.
What's Missing
Parallel paths
- 🟡
noop_agent.pyand out-of-tree SPI agents don't emitsystem_prompt_semantics, butREPORT_SCHEMA.mddocuments absence as a specific regime — triggered by the threeget_environment_info()overrides. - ✅ Renderers verified not divergent:
reports_html.pyandreports_experiment.pyshare the fixedcollect_agent_settings_rows;reports_stats.py,reports_junit.py, andevalboard/have zero references.
Tests
- 🟠 No test loads
replace+system_prompt_filethrough the real loader — which is why blocker 1 ships green. - 🟡 Antigravity's
system_instructionsforwarding untested, with no CI extra. - 🔵 Codex SDK-signature drift;
_effective_prompt_modefail-open and_unwrap_system_promptfallthrough untested.
Downstream consumers
- 🟠 External consumers in
coder-eval-uipath/ eval-runner that string-handlesdk_options.system_promptbreak on thestr → dictchange. There's no in-repo canary (nothing in-repo reads it directly), so theREPORT_SCHEMA.mdprose warning is the only signal — worth a heads-up to that repo's owners.
Display & mapping dicts
- Nothing identified — the new
Literalhas no icon/label/counting map, and its three branch sites were checked for exhaustiveness.
Nightly impact — answered explicitly: no container rebuild, no schema migration. docker_runner.py is untouched and prompt-agnostic; the Bedrock judge route bypasses the SDK. One residual: the prompt baseline now tracks the pinned CLAUDE_CODE_VERSION=2.1.177, so bumping it becomes a score-affecting change (correctly attributable via environment_info.claude_code_cli).
Harness & Lint Improvements
Static checks (lint / type)
- Extend CE030 to the agent configs (
ClaudeCodeAgentConfig→CLAUDE_CODE.md,CodexAgentConfig→CODEX.md, antigravity →ANTIGRAVITY.md, plusBaseAgentConfig) — recommended last round, still open; would gate the newsystem_prompt_modedoc row. - New
CEnnn: every class registered inAgentRegistrymust emitsystem_prompt_semanticsfromget_environment_info()(registry-based whole-tree check, same shape as CE025) — or give the base a default so the rule isn't needed. - New
CEnnn: forbid bareassertas type-narrowing insrc/coder_eval/agents/, where the codebase's own convention is stated (sub_agent.py:82-84) but unenforced. Would catch the_build_optionsassert (also the one new bandit B101 delta). - A shared
SystemPromptSemanticsLiteralalias — type tightening rather than a rule. - Not statically reachable: the load-order crash needs Pydantic's
validate_assignmentruntime behavior; the migration and A/B-lever items need semantic judgment.
Harness improvements
- A loader-level matrix over
system_prompt×system_prompt_mode×system_prompt_filerun throughload_task— the construction-only test is what let the crash ship green. - Add the
antigravityextra topr-checks.yml, or extract_build_local_agent_kwargs()so the forwarding is assertable without the SDK. - An
inspect.signature(thread_start)assertion fordeveloper_instructions. - A
log.warningon_effective_prompt_mode's fail-open branch.
Top 5 Priority Actions
- Fix the
replace+system_prompt_fileload crash — batch both field updates into onemodel_copy(update=...)inresolve_agent_system_promptso no half-updated state is validated, then add a loader-level test for the combination. - Re-baseline or pin
echo_simulated_judged.yaml— setsystem_prompt_mode: replaceto preserve its baseline, or reword it for append semantics and note the re-baseline. - Add the migration surface — a "Migrating tasks that set
system_prompt" callout inCLAUDE_CODE.md, a "Migration" note inCODEX.md(ignored → injected, with no opt-out onCodexAgentConfig), and aBREAKING CHANGE:footer so it lands inCHANGELOG.md. - Make the
system_prompt_semanticscontract true — giveAgent.get_environment_info()a base default (or stamp it in the orchestrator) so every agent emits it, and correctREPORT_SCHEMA.mdso "absent" means "unknown" rather than "pre-append regime". - Close the two small correctness gaps — reject empty/whitespace-only prompts under
replace, and tighten theSubAgentRunnerguard to require both a prompt andreplacemode.
Change class: complex — it changes prompt-construction semantics on a shared config field across three agents, adds a validator and two guards, and alters a persisted run-record wire format; correctness requires reasoning about consumers outside the diff.
Stats: 0 🔴 · 2 🟠 · 7 🟡 · 6 🔵 across 8 axes reviewed. (Previous review: 1 🔴 · 4 🟠 · 2 🟡 · 5 🔵.)
Why append instead of replace
ClaudeAgentOptions.system_promptaccepts either a plain string or theclaude_codepreset dict. A plain string replaces Claude Code's entire default system prompt — the only way to keep the default is{"type": "preset", "preset": "claude_code", "append": ...}. coder_eval passes the experiment'ssystem_promptstraight through as a string, so any experiment that sets even a one-line prompt silently strips every behavioral instruction the harness ships with.That is exactly what the skills-repo experiments do. The nightly config sets an innocuous sandbox guard:
https://github.com/UiPath/skills/blob/main/tests/experiments/nightly.yaml#L39-L40
(same pattern in
tests/experiments/default.yaml#L21,smoke.yaml#L39,smoke-windows.yaml#L23, and the skill-comparison templates)One sentence of sandbox policy costs the whole Claude Code system prompt.
Observed impact (skills nightly,
skill-rpa-execution-map-greenfield)The task is a turn-budget gate (
max_turns: 10, expected 6) that assumes the agent batches tool calls per turn. Everyclaude-sonnet-5run exhausted the cap; pass/fail depended on where the cap happened to land. Transcript analysis across four runs (31172161551, 31174117722 ×3 attempts, 31178128981, 31179344004 ×2 attempts):Bashcat/sed/findwhere the default prompt directs the dedicatedRead/Grep/Globtools (e.g.cd TextReport && cat project.json && cat Main.xaml), losing the harness's file-tracking and permission integration.Beyond the observed items, replacing the prompt also drops the default guidance on code-reference formatting, task management, professional tone, and the security guardrails — none of which an experiment author intends to disable when adding a sandbox-scoping sentence.
The change
claude_code_agent.py: whensystem_promptis configured, wrap it asSystemPromptPreset(type="preset", preset="claude_code", append=...)so the default prompt survives and the experiment text is appended.Nonestill means the untouched SDK default.agent_config.py:system_promptfield description updated ("appended to the agent's default system prompt" — previously "Replaces").tests/test_agent.py: two tests via the existing_capture_sdk_optionspattern (append wrapping;Nonepassthrough).Behavioral note for existing consumers
Every experiment that sets
system_promptswitches from replace to append semantics with this release. For the known consumers (sandbox-scoping one-liners) this is the intended repair. An experiment that deliberately relied on full replacement to suppress default Claude Code behavior would need a different mechanism.Judge (
agent_judge.py) and user-simulator paths construct their own options and are unaffected.🤖 Generated with Claude Code