You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Full-mode open_kitchen payloads must reach the orchestrator untruncated — recipe payloads are allowed to be very large; delivering them whole is the product requirement. On Codex code-mode sessions this does not hold: a remediation orchestrator session on 2026-07-20 received only ~10,000 of 43,857 tokens of its open_kitchen response. Most of the recipe body never reached the model, and the errors/diagram/suggestions fields were cut entirely; the tail of the response (ingredients_table, orchestration_rules, stop_step_semantics, deferred_guards, closing metadata) survived the middle-out cut — by serialization-order luck, not by design.
Observed Incident (direct log evidence)
Codex rollout log: ~/.codex/sessions/2026/07/20/rollout-2026-07-20T07-08-43-019f7fdb-8f14-7200-88ad-225434150b08.jsonl (interactive remediation orchestrator, started 2026-07-20T14:08:43Z).
with a middle-out cut marker …33857 tokens truncated… — exactly 10,000 tokens kept. Head: start of recipe content. Tail: ingredients_table, orchestration_rules, stop_step_semantics, deferred_guards, closing metadata (verified byte positions in the visible output: cut marker 20,079; ingredients_table 23,790; orchestration_rules 26,161; stop_step_semantics 30,096). Lost in the cut: errors, diagram, suggestions, and most of content (the recipe body). Which fields survive is a function of serialization order and payload size — nothing guarantees it.
No max_output_tokens was passed — verified against the full rollout: the string's only occurrence in the entire session is the advisory intake-discipline digest text inside the prompt.
Call 2 (14:09:05Z) — the model improvised a recovery call open_kitchen({name:"remediation", ingredients_only:true}) (15,195 tokens, also truncated to ~10K, but the metadata fields survived). The visible "double open_kitchen call" is a symptom of this bug, not a defect itself.
Root Cause
Every autoskillit-side budget layer passed the payload intentionally, and the one layer that cut it is outside all of them:
Codex transport ceiling — CODEX_TOOL_OUTPUT_TOKEN_LIMIT (src/autoskillit/execution/backends/_codex_config.py:34, ((186_000+3)//4)+8_000) written by autoskillit init to ~/.codex/config.toml as tool_output_token_limit = 54500, deliberately sized for the open_kitchen exemption (ADR-0005 "Per-Repo Ceiling Guidance")
54,500 tokens
43,857 < 54,500 → should fit
Codex code-mode path
model-declared max_output_tokens governs; config ceiling is not the operative bound (ADR-0005, docs/decisions/0005-output-budget-protocol.md: "code-mode models (gpt-5.6-sol) honor model-declared max_output_tokens unclamped — for those sessions the ceiling is not a hard cap")
model declared nothing (no max_output_tokens anywhere in the rollout) → an exactly-10,000-token bound governed the output. The enforcing mechanism lives in Codex's code-mode harness, outside this repo, and is unconfirmed — the figure matches the digest's advisory 10,000, but whether it is a harness default or model-side behavior cannot be proven from the rollout
ADR-0005 documented the code-mode bypass in the upward direction (unclamped intake → context blowouts, #4272/#4280). This incident is the complementary downward direction: when the model passes no max_output_tokens, an effective ~10K bound governs — below the configured 54,500 ceiling that was specifically engineered so open_kitchen would fit. The only autoskillit-side control on this path is the advisory digest line "Never pass max_output_tokens above 10000" (CODEX_INTAKE_DISCIPLINE_DIGEST, src/autoskillit/core/types/_type_constants.py:174) — prompt-level, unenforced, and its 10,000 figure is itself far below full-mode open_kitchen size.
Ruled-out alternative explanations (verified during adversarial review): (a) a per-session CODEX_HOME/config override — the interactive launcher calls build_interactive_cmd without add_dirs (src/autoskillit/cli/session/_session_launch.py:109-117), so the CODEX_HOME override branch in src/autoskillit/execution/backends/codex.py never fires for orchestrator sessions; the active config was ~/.codex/config.toml with tool_output_token_limit = 54500. (b) an autoskillit-side truncation hook — no code in server/ or execution/ touches MCP response bytes for Codex sessions.
Impact
The orchestrator ran its pipeline with most of the recipe body (~3/4 of the payload) missing from context and the validity signals (errors, suggestions, diagram) cut entirely — deviation risk. The orchestration rules and stop-step semantics survived this particular cut, but only because they serialize near the tail; the middle-out cut guarantees nothing about which fields survive at other payload sizes.
Structural for large recipes: full-mode remediation is ~43.9K tokens vs. the observed ~10K delivery bound, and ADR-0005's measured maximum full-mode payload is 183,103 bytes (≈45.8K tokens by the 4-byte heuristic) — so any Codex-backend session opening a recipe whose full-mode payload exceeds ~10K tokens is affected. Small bundled recipes (e.g. consolidate-health-reports.yaml, 1,952 bytes) plausibly fit under the bound. Codex is not the default interactive backend (AgentBackendConfig.backend defaults to "claude-code", src/autoskillit/config/_config_dataclasses.py:607); the defect applies to sessions explicitly launched on the Codex backend.
Secondary waste: forced recovery double-call; validity information (errors, suggestions) invisible on the first call.
Prior Manifestations — 4th occurrence of this failure class
Related open issues (opposite failure direction, same design weakness): #4272, #4280. Distinct kitchen-tracker defect in the same area: #4293.
Invariant (acceptance criteria)
A Codex interactive orchestrator session receives the complete full-mode open_kitchen payload (content, orchestration_rules, stop_step_semantics, errors, diagram, suggestions, ingredients_table) in one call, untruncated, for every bundled recipe.
The invariant is regression-gated: an end-to-end assertion (analog of ADR-0005's live large-output probe) that covers the code-mode default (downward) direction, not only the unclamped (upward) one.
Feasibility note: criterion 1 is achievable relative to the configured ceilings — ADR-0005's measured maximum full-mode payload (183,103 bytes, remediation all-truthy) sits under the 186,000-byte exemption, and ≈45.8K tokens sits under the 54,500-token config ceiling — provided the fix makes that ceiling (or better) the operative bound on the code-mode path. Until then, the bound to beat is the observed 10,000 tokens, not 54,500: note that even the recovery payload (ingredients_only=true, 15,195 tokens) exceeded 10K and was truncated too.
Candidate Fix Directions (not prescriptive)
Enforce, don't advise: ensure code-mode MCP calls for backstop-exempted tools carry an effective max_output_tokens ≥ CODEX_TOOL_OUTPUT_TOKEN_LIMIT, rather than relying on the prompt-level digest. Caveat — likely upstream-dependent: autoskillit currently has no control surface over the code-mode harness (zero code-mode references anywhere in src/autoskillit/; the only knobs it writes are config.toml keys — and ADR-0005 states tool_output_token_limit does not clamp code mode — plus advisory prompt text). Unless Codex exposes such a mechanism, this direction requires an upstream change (cf. ADR-0006: "Route (c) — upstream pre-truncation integration — is the ideal end state but outside this repo's control"). In-repo, the enforceable options are direction 2 and hardened advisory prompting.
Or serve to fit: a Codex-code-mode-sized full-mode serving (compact variant or chunked delivery) that stays under the path's operative bound.
Either way, extend ADR-0005's release gates so the open_kitchen-fits-the-transport relationship is pinned for the code-mode path specifically.
Investigation
Prior investigation completed interactively. See below for root cause analysis.
Investigation: Double open_kitchen Call + Warning Suggestions in the Second Response (Codex Orchestrator)
Date: 2026-07-20 Scope: Why a Codex-backend remediation orchestrator session called open_kitchen twice (full mode, then ingredients_only=true), and whether the "error" content in the second response indicates a defect Mode: Standard
Summary
The pasted transcript came from Codex-backend orchestrator session 019f7fdb-8f14-7200-88ad-225434150b08 (started 2026-07-20 14:08:43 UTC, rollout log at ~/.codex/sessions/2026/07/20/rollout-2026-07-20T07-08-43-019f7fdb-8f14-7200-88ad-225434150b08.jsonl). The orchestrator prompt prescribes exactly one full open_kitchen(name='remediation') call. The model made that call, but the ~43,857-token response was middle-out truncated to ~10,000 tokens by the Codex code-mode exec harness ("Warning: truncated output (original token count: 43857)" / "…33857 tokens truncated…"). The cut removed the errors, diagram, and suggestions fields and most of the recipe content, leaving the model unable to verify recipe validity; the response tail — ingredients_table, orchestration_rules, stop_step_semantics, deferred_guards, and closing metadata — survived the middle-out cut (verified byte positions in the visible output: cut marker 20,079; ingredients_table 23,790; orchestration_rules 26,161; stop_step_semantics 30,096). The second call with ingredients_only=true was the model's improvised recovery — a smaller payload (15,195 tokens, also truncated but with all metadata fields surviving). The "error" in the second response is actually "errors": [] (empty — no errors) plus two WARNING-severity advisory suggestions that were present in both responses but only visible in the second, because the first response's truncation cut them out. Neither warning blocks anything; one is systemic advisory noise, the other flags a real but repo-wide routing gap.
Root Cause
Question 1 — the double call. Not by design for the interactive flow, and not a tool-contract bug: the second call was rational model-improvised recovery from harness truncation.
The session received the standard interactive orchestrator prompt (_build_orchestrator_prompt, src/autoskillit/cli/_prompts_orchestrator.py:112-123): "FIRST ACTION … Call open_kitchen(name='remediation')" — a single call, with a pre-rendered ingredients table already embedded in the prompt ("RECIPE INGREDIENTS — USE THESE EXACT NAMES"). Both facts confirmed present in the rollout log.
Both response modes return the same underlying payload; ingredients_only=true merely strips {"content", "orchestration_rules", "stop_step_semantics"} (strip_ingredients_only_keys, src/autoskillit/server/_misc.py:234-239; applied at src/autoskillit/server/tools/tools_kitchen.py:1053-1054, 1179-1180). The full-mode response already contains ingredients_table, diagram, suggestions, errors. There is no server-side reason to call twice.
What actually happened (direct rollout-log evidence): call 1 at 14:08:56 returned 43,857 tokens; the Codex code-mode exec harness middle-out truncated it to ~10,000 tokens, keeping the head (start of recipe content) and tail (which happened to include the complete ingredients_table, orchestration_rules, stop_step_semantics, deferred_guards, and the closing metadata) while cutting 33,857 tokens from the middle — including errors, diagram, and suggestions. Call 2 at 14:09:05 (ingredients_only:true) returned 15,195 tokens, also truncated, but small enough that errors/diagram/suggestions/ingredients_table all survived.
Why the harness truncated: both server-side guards were satisfied — the ~175KB payload is within the RESPONSE_BACKSTOP_EXEMPTION_REGISTRY["open_kitchen"] ceiling of 186,000 bytes (src/autoskillit/core/types/_type_constants_registries.py:191-202) and below CODEX_TOOL_OUTPUT_TOKEN_LIMIT = 54,500 (_codex_config.py:34, ADR-0005). The exec call passed nomax_output_tokens at all — verified: the string's only occurrence in the entire rollout is the advisory digest text in the prompt (CODEX_INTAKE_DISCIPLINE_DIGEST, src/autoskillit/core/types/_type_constants.py:174). The ~10K bound is therefore the Codex code-mode harness's default output cap for script output when the model requests nothing larger. This is the flip side of the behavior ADR-0005 (lines 108–111) documents — model-declared max_output_tokens is honored unclamped: the bound is model-discretionary in both directions. Ask for too much and context blows out (the earlier 019f6b5a incident); ask for nothing and the default cap silently drops 3/4 of the payload (this incident).
Question 2 — the "error" in the second response. There is no error. "errors": [] is empty; errors is reserved for structural/parse/route failures (src/autoskillit/recipe/_api.py:370-431, 556-587). The visible items are two WARNING-severity suggestions emitted by semantic rules that run on every open_kitchen (via serve_recipe → load_and_validate → run_semantic_rules, _api.py:475):
required-ingredient-no-default on task (src/autoskillit/recipe/rules/rules_inputs.py:292-318, WARNING): fires because task is required: true with no default and not hidden. Every bundled recipe's primary task-type ingredient shares this exact shape (implementation.yaml, planner.yaml, all research*.yaml, etc.) — it is the intentional, universal convention, since "what to investigate" has no sensible default. Advisory noise for this ingredient category.
file-writing-skill-missing-context-limit on rectify and dry_walkthrough (src/autoskillit/recipe/rules/rules_worktree.py:178-227, WARNING): factually correct — remediation.yamlrectify (lines 289–315) and dry_walkthrough (lines 334–351) invoke write_behavior: always skills with no on_context_limit route. But the identical gap exists in implementation.yaml's plan/verify steps, while both recipes DO wire on_context_limit: retry_worktree for implement (remediation.yaml:380, implementation.yaml:339). A real, systemic, low-priority gap — not a remediation-specific regression.
Neither warning affects valid (compute_recipe_validity, registry.py:256-265, counts only ERROR severity) or gates anything downstream. The reason they seemed to appear only in the second call: they were in the truncated middle region of the first response.
src/autoskillit/cli/_prompts_orchestrator.py:112-123: single-call FIRST ACTION prescription [SUPPORTED]
src/autoskillit/core/types/_type_constants.py:174 (CODEX_INTAKE_DISCIPLINE_DIGEST), _type_constants_registries.py:191-202 (backstop exemption), _codex_config.py:34 (Codex transport limit): the three layers whose interplay allowed the truncation [SUPPORTED]
Data Flow
open_kitchen(name) → serve_recipe → RecipeRepository.load_and_validate (repository.py:87-124 → _api.py:188) builds the full result dict (content ≈ 40K tokens, errors, diagram, suggestions, valid, ingredients_table, orchestration_rules, …) → optional strip_ingredients_only_keys → MCP response (within the 186KB server-side exemption budget) → Codex code-mode exec harness (tools.mcp__autoskillit__open_kitchen(...) inside a JS exec call) → middle-out truncation to ~10K tokens → model context. The truncation boundary is the only layer that cut the payload; every server-side layer passed it intentionally.
Test Gap Analysis
No test exercises a full-mode open_kitchen response traversing the Codex code-mode exec harness — the four output-budget layers are each tested in isolation; the end-to-end "full recipe payload reaches the orchestrator on any backend in one call" invariant has no test.
tests/recipe/test_bundled_recipes_all_truthy.py filters suggestions to severity == "error" only, so warnings on bundled recipes pass silently and accumulate; no zero-warning or allowlist assertion exists for remediation.yaml.
Each open_kitchen mode is tested independently (tests/server/test_tools_kitchen_gate_features.py:119-163, test_tools_kitchen_envelope.py); no sequential two-call workflow test exists (closest is test_tools_kitchen_cache_poison.py, which is cache-integrity framed).
Similar Patterns
The implement step's on_context_limit: retry_worktree route (remediation.yaml:380, implementation.yaml:339) is the established pattern the warning rule asks for; recipe authors applied it where stranded writes are most costly (worktree mutation) and left plan/markdown-writing steps (rectify, dry_walkthrough, plan, verify) unrouted. For payload-size collisions, the established mitigations are per-path: ingredients_only (dispatch), deferred-recall stripping (Codex pre-reveal), RESPONSE_BACKSTOP_EXEMPTION_REGISTRY (Claude Code disk gate) — each fixed one boundary at a time.
ADR-0005 itself frames recipe-payload/boundary collisions as "the recurring failure class behind #4253, #2819, #2564, #3938."
This is a recurring pattern — consider running /rectify for architectural immunity after resolving the immediate issue.
An operational consequence worth noting: the Codex orchestrator in session 019f7fdb proceeded with only ~10K of 44K tokens of recipe context — most of the recipe body never reached the model (the orchestration rules, ingredients table, and deferred_guards survived in the truncated tail — by serialization-order luck, not by design), which still raises deviation risk for the rest of that pipeline run.
External Research
Not applicable — all evidence was local (session rollout logs, source, git history, GitHub issues via gh). Prior internal investigation of Codex context behavior (session 12326fb2, 2026-07-20 06:48, re: Codex session 019f6b5a) established that code-mode honors model-supplied max_output_tokens without an upper clamp — the unbounded intake failure direction. That does not conflict with this incident: here no max_output_tokens was supplied and the harness's default cap applied. Same root design (model-discretionary bound, no enforced config-level control), opposite symptom.
Scope Boundary
Investigated: the Codex rollout log for the session in question; open_kitchen implementation and both response modes; the two semantic rules and their severities; remediation.yaml ground truth vs. other bundled recipes; orchestrator/kitchen prompt builders; introducing commits for all mechanisms; the four output-budget layers; GitHub issue history; test coverage. Not yet explored: Codex-source confirmation of the exact default that produced the ~10K cut in this specific session (model-declared max_output_tokens per the digest vs. a harness default — the digest rule matches the observed bound, but the model's encrypted reasoning prevents direct confirmation); whether other MCP tool responses in that session were similarly truncated; downstream behavior of session 019f7fdb after the user's task was provided.
Recommendations
File a tracking issue for the real defect: full-mode open_kitchen responses exceed what the Codex code-mode exec harness will deliver, so the Codex interactive orchestrator can never receive the complete recipe payload in one call. The fix direction suggested by ADR-0005's own analysis: an enforced (config/harness-level) transport bound or max_output_tokens floor for MCP calls in code mode — analogous to the ADR-0006 shell_capture_hook pattern — rather than the current prompt-level advisory digest; or per-backend serving that keeps the full-mode payload under the harness bound (e.g., a compact full-mode variant for Codex).
Do not "fix" the double call itself — it was rational recovery and is cheap relative to the alternative (operating blind). Fixing the truncation removes the trigger.
Warnings: treat required-ingredient-no-default on task-type ingredients as advisory noise (optionally exempt task-category ingredients from the rule, or accept the warning); the file-writing-skill-missing-context-limit findings are legitimate — if addressed, add on_context_limit routes to rectify/dry_walkthrough (and implementation.yamlplan/verify) following the existing implement → retry_worktree pattern, as a deliberate decision rather than warning suppression.
Test hardening: an end-to-end assertion that bundled full-mode payloads fit every backend's delivery bound (the invariant ADR-0005 gestures at), and an allowlist-based zero-new-warnings test for bundled recipes so new WARNING-rule firings surface at PR time instead of at kitchen-open time.
Confidence Levels
Second call caused by first-response truncation (sequence + truncation warnings + cut field inventory): SUPPORTED
Single-call design for interactive flow; ingredients_only intended for dispatch: SUPPORTED
"errors": [] + WARNING suggestions is normal, non-blocking output: SUPPORTED
Warnings present in both responses; hidden in the first by the middle cut: SUPPORTED
Missing on_context_limit on rectify/dry_walkthrough is real but systemic: SUPPORTED
No explicit max_output_tokens on the exec calls; the ~10K cut is the harness default cap: SUPPORTED (verified against the full rollout log)
The exact Codex-source constant implementing that default cap: NEEDS-EVIDENCE (lives in Codex source, outside this repo)
Model's motivation for the recovery call (recover the cut errors/suggestions/diagram validity signals): NEEDS-EVIDENCE (inferred from the truncation warning; reasoning not inspectable)
Summary
Full-mode
open_kitchenpayloads must reach the orchestrator untruncated — recipe payloads are allowed to be very large; delivering them whole is the product requirement. On Codex code-mode sessions this does not hold: a remediation orchestrator session on 2026-07-20 received only ~10,000 of 43,857 tokens of itsopen_kitchenresponse. Most of the recipe body never reached the model, and theerrors/diagram/suggestionsfields were cut entirely; the tail of the response (ingredients_table,orchestration_rules,stop_step_semantics,deferred_guards, closing metadata) survived the middle-out cut — by serialization-order luck, not by design.Observed Incident (direct log evidence)
Codex rollout log:
~/.codex/sessions/2026/07/20/rollout-2026-07-20T07-08-43-019f7fdb-8f14-7200-88ad-225434150b08.jsonl(interactive remediation orchestrator, started 2026-07-20T14:08:43Z).Call 1 (14:08:56Z) — code-mode exec script:
Harness-rendered result (verbatim):
with a middle-out cut marker
…33857 tokens truncated…— exactly 10,000 tokens kept. Head: start of recipecontent. Tail:ingredients_table,orchestration_rules,stop_step_semantics,deferred_guards, closing metadata (verified byte positions in the visible output: cut marker 20,079; ingredients_table 23,790; orchestration_rules 26,161; stop_step_semantics 30,096). Lost in the cut:errors,diagram,suggestions, and most ofcontent(the recipe body). Which fields survive is a function of serialization order and payload size — nothing guarantees it.No
max_output_tokenswas passed — verified against the full rollout: the string's only occurrence in the entire session is the advisory intake-discipline digest text inside the prompt.Call 2 (14:09:05Z) — the model improvised a recovery call
open_kitchen({name:"remediation", ingredients_only:true})(15,195 tokens, also truncated to ~10K, but the metadata fields survived). The visible "double open_kitchen call" is a symptom of this bug, not a defect itself.Root Cause
Every autoskillit-side budget layer passed the payload intentionally, and the one layer that cut it is outside all of them:
RESPONSE_BACKSTOP_EXEMPTION_REGISTRY["open_kitchen"](src/autoskillit/core/types/_type_constants_registries.py:197-199)CODEX_TOOL_OUTPUT_TOKEN_LIMIT(src/autoskillit/execution/backends/_codex_config.py:34,((186_000+3)//4)+8_000) written byautoskillit initto~/.codex/config.tomlastool_output_token_limit = 54500, deliberately sized for theopen_kitchenexemption (ADR-0005 "Per-Repo Ceiling Guidance")max_output_tokensgoverns; config ceiling is not the operative bound (ADR-0005,docs/decisions/0005-output-budget-protocol.md: "code-mode models (gpt-5.6-sol) honor model-declaredmax_output_tokensunclamped — for those sessions the ceiling is not a hard cap")max_output_tokensanywhere in the rollout) → an exactly-10,000-token bound governed the output. The enforcing mechanism lives in Codex's code-mode harness, outside this repo, and is unconfirmed — the figure matches the digest's advisory 10,000, but whether it is a harness default or model-side behavior cannot be proven from the rolloutADR-0005 documented the code-mode bypass in the upward direction (unclamped intake → context blowouts, #4272/#4280). This incident is the complementary downward direction: when the model passes no
max_output_tokens, an effective ~10K bound governs — below the configured 54,500 ceiling that was specifically engineered soopen_kitchenwould fit. The only autoskillit-side control on this path is the advisory digest line "Never pass max_output_tokens above 10000" (CODEX_INTAKE_DISCIPLINE_DIGEST,src/autoskillit/core/types/_type_constants.py:174) — prompt-level, unenforced, and its 10,000 figure is itself far below full-modeopen_kitchensize.Ruled-out alternative explanations (verified during adversarial review): (a) a per-session
CODEX_HOME/config override — the interactive launcher callsbuild_interactive_cmdwithoutadd_dirs(src/autoskillit/cli/session/_session_launch.py:109-117), so theCODEX_HOMEoverride branch insrc/autoskillit/execution/backends/codex.pynever fires for orchestrator sessions; the active config was~/.codex/config.tomlwithtool_output_token_limit = 54500. (b) an autoskillit-side truncation hook — no code inserver/orexecution/touches MCP response bytes for Codex sessions.Impact
errors,suggestions,diagram) cut entirely — deviation risk. The orchestration rules and stop-step semantics survived this particular cut, but only because they serialize near the tail; the middle-out cut guarantees nothing about which fields survive at other payload sizes.remediationis ~43.9K tokens vs. the observed ~10K delivery bound, and ADR-0005's measured maximum full-mode payload is 183,103 bytes (≈45.8K tokens by the 4-byte heuristic) — so any Codex-backend session opening a recipe whose full-mode payload exceeds ~10K tokens is affected. Small bundled recipes (e.g.consolidate-health-reports.yaml, 1,952 bytes) plausibly fit under the bound. Codex is not the default interactive backend (AgentBackendConfig.backenddefaults to"claude-code",src/autoskillit/config/_config_dataclasses.py:607); the defect applies to sessions explicitly launched on the Codex backend.errors,suggestions) invisible on the first call.Prior Manifestations — 4th occurrence of this failure class
ingredients_onlyRelated open issues (opposite failure direction, same design weakness): #4272, #4280. Distinct kitchen-tracker defect in the same area: #4293.
Invariant (acceptance criteria)
open_kitchenpayload (content,orchestration_rules,stop_step_semantics,errors,diagram,suggestions,ingredients_table) in one call, untruncated, for every bundled recipe.Feasibility note: criterion 1 is achievable relative to the configured ceilings — ADR-0005's measured maximum full-mode payload (183,103 bytes,
remediationall-truthy) sits under the 186,000-byte exemption, and ≈45.8K tokens sits under the 54,500-token config ceiling — provided the fix makes that ceiling (or better) the operative bound on the code-mode path. Until then, the bound to beat is the observed 10,000 tokens, not 54,500: note that even the recovery payload (ingredients_only=true, 15,195 tokens) exceeded 10K and was truncated too.Candidate Fix Directions (not prescriptive)
max_output_tokens≥CODEX_TOOL_OUTPUT_TOKEN_LIMIT, rather than relying on the prompt-level digest. Caveat — likely upstream-dependent: autoskillit currently has no control surface over the code-mode harness (zero code-mode references anywhere insrc/autoskillit/; the only knobs it writes areconfig.tomlkeys — and ADR-0005 statestool_output_token_limitdoes not clamp code mode — plus advisory prompt text). Unless Codex exposes such a mechanism, this direction requires an upstream change (cf. ADR-0006: "Route (c) — upstream pre-truncation integration — is the ideal end state but outside this repo's control"). In-repo, the enforceable options are direction 2 and hardened advisory prompting.open_kitchen-fits-the-transport relationship is pinned for the code-mode path specifically.Investigation
Investigation: Double open_kitchen Call + Warning Suggestions in the Second Response (Codex Orchestrator)
Date: 2026-07-20
Scope: Why a Codex-backend remediation orchestrator session called
open_kitchentwice (full mode, theningredients_only=true), and whether the "error" content in the second response indicates a defectMode: Standard
Summary
The pasted transcript came from Codex-backend orchestrator session
019f7fdb-8f14-7200-88ad-225434150b08(started 2026-07-20 14:08:43 UTC, rollout log at~/.codex/sessions/2026/07/20/rollout-2026-07-20T07-08-43-019f7fdb-8f14-7200-88ad-225434150b08.jsonl). The orchestrator prompt prescribes exactly one fullopen_kitchen(name='remediation')call. The model made that call, but the ~43,857-token response was middle-out truncated to ~10,000 tokens by the Codex code-mode exec harness ("Warning: truncated output (original token count: 43857)" / "…33857 tokens truncated…"). The cut removed theerrors,diagram, andsuggestionsfields and most of the recipe content, leaving the model unable to verify recipe validity; the response tail —ingredients_table,orchestration_rules,stop_step_semantics,deferred_guards, and closing metadata — survived the middle-out cut (verified byte positions in the visible output: cut marker 20,079; ingredients_table 23,790; orchestration_rules 26,161; stop_step_semantics 30,096). The second call withingredients_only=truewas the model's improvised recovery — a smaller payload (15,195 tokens, also truncated but with all metadata fields surviving). The "error" in the second response is actually"errors": [](empty — no errors) plus two WARNING-severity advisory suggestions that were present in both responses but only visible in the second, because the first response's truncation cut them out. Neither warning blocks anything; one is systemic advisory noise, the other flags a real but repo-wide routing gap.Root Cause
Question 1 — the double call. Not by design for the interactive flow, and not a tool-contract bug: the second call was rational model-improvised recovery from harness truncation.
_build_orchestrator_prompt,src/autoskillit/cli/_prompts_orchestrator.py:112-123): "FIRST ACTION … Call open_kitchen(name='remediation')" — a single call, with a pre-rendered ingredients table already embedded in the prompt ("RECIPE INGREDIENTS — USE THESE EXACT NAMES"). Both facts confirmed present in the rollout log.ingredients_only=truemerely strips{"content", "orchestration_rules", "stop_step_semantics"}(strip_ingredients_only_keys,src/autoskillit/server/_misc.py:234-239; applied atsrc/autoskillit/server/tools/tools_kitchen.py:1053-1054, 1179-1180). The full-mode response already containsingredients_table,diagram,suggestions,errors. There is no server-side reason to call twice.content) and tail (which happened to include the completeingredients_table,orchestration_rules,stop_step_semantics,deferred_guards, and the closing metadata) while cutting 33,857 tokens from the middle — includingerrors,diagram, andsuggestions. Call 2 at 14:09:05 (ingredients_only:true) returned 15,195 tokens, also truncated, but small enough thaterrors/diagram/suggestions/ingredients_tableall survived.RESPONSE_BACKSTOP_EXEMPTION_REGISTRY["open_kitchen"]ceiling of 186,000 bytes (src/autoskillit/core/types/_type_constants_registries.py:191-202) and belowCODEX_TOOL_OUTPUT_TOKEN_LIMIT = 54,500(_codex_config.py:34, ADR-0005). The exec call passed nomax_output_tokensat all — verified: the string's only occurrence in the entire rollout is the advisory digest text in the prompt (CODEX_INTAKE_DISCIPLINE_DIGEST,src/autoskillit/core/types/_type_constants.py:174). The ~10K bound is therefore the Codex code-mode harness's default output cap for script output when the model requests nothing larger. This is the flip side of the behavior ADR-0005 (lines 108–111) documents — model-declaredmax_output_tokensis honored unclamped: the bound is model-discretionary in both directions. Ask for too much and context blows out (the earlier019f6b5aincident); ask for nothing and the default cap silently drops 3/4 of the payload (this incident).Question 2 — the "error" in the second response. There is no error.
"errors": []is empty;errorsis reserved for structural/parse/route failures (src/autoskillit/recipe/_api.py:370-431, 556-587). The visible items are two WARNING-severitysuggestionsemitted by semantic rules that run on everyopen_kitchen(viaserve_recipe→load_and_validate→run_semantic_rules,_api.py:475):required-ingredient-no-defaultontask(src/autoskillit/recipe/rules/rules_inputs.py:292-318, WARNING): fires becausetaskisrequired: truewith no default and not hidden. Every bundled recipe's primary task-type ingredient shares this exact shape (implementation.yaml,planner.yaml, allresearch*.yaml, etc.) — it is the intentional, universal convention, since "what to investigate" has no sensible default. Advisory noise for this ingredient category.file-writing-skill-missing-context-limitonrectifyanddry_walkthrough(src/autoskillit/recipe/rules/rules_worktree.py:178-227, WARNING): factually correct —remediation.yamlrectify(lines 289–315) anddry_walkthrough(lines 334–351) invokewrite_behavior: alwaysskills with noon_context_limitroute. But the identical gap exists inimplementation.yaml'splan/verifysteps, while both recipes DO wireon_context_limit: retry_worktreeforimplement(remediation.yaml:380,implementation.yaml:339). A real, systemic, low-priority gap — not a remediation-specific regression.Neither warning affects
valid(compute_recipe_validity,registry.py:256-265, counts only ERROR severity) or gates anything downstream. The reason they seemed to appear only in the second call: they were in the truncated middle region of the first response.Affected Components
~/.codex/sessions/2026/07/20/rollout-2026-07-20T07-08-43-019f7fdb….jsonl: primary evidence — both calls, truncation warnings, cut markers [SUPPORTED]src/autoskillit/server/tools/tools_kitchen.py:765-1268: open_kitchen implementation; ingredients_only stripping at 1053-1054, 1179-1180 [SUPPORTED]src/autoskillit/server/_misc.py:234-239:strip_ingredients_only_keys— strips only content/orchestration_rules/stop_step_semantics [SUPPORTED]src/autoskillit/recipe/_api.py:188-660:load_and_validate— builds errors/suggestions/ingredients_table on every call [SUPPORTED]src/autoskillit/recipe/rules/rules_inputs.py:292-318,rules_worktree.py:178-227: the two WARNING rules [SUPPORTED]src/autoskillit/recipes/remediation.yaml:38-40, 289-315, 334-351, 380: task ingredient; rectify/dry_walkthrough (no on_context_limit); implement (has it) [SUPPORTED]src/autoskillit/cli/_prompts_orchestrator.py:112-123: single-call FIRST ACTION prescription [SUPPORTED]src/autoskillit/core/types/_type_constants.py:174(CODEX_INTAKE_DISCIPLINE_DIGEST),_type_constants_registries.py:191-202(backstop exemption),_codex_config.py:34(Codex transport limit): the three layers whose interplay allowed the truncation [SUPPORTED]Data Flow
open_kitchen(name)→serve_recipe→RecipeRepository.load_and_validate(repository.py:87-124→_api.py:188) builds the full result dict (content ≈ 40K tokens, errors, diagram, suggestions, valid, ingredients_table, orchestration_rules, …) → optionalstrip_ingredients_only_keys→ MCP response (within the 186KB server-side exemption budget) → Codex code-mode exec harness (tools.mcp__autoskillit__open_kitchen(...)inside a JSexeccall) → middle-out truncation to ~10K tokens → model context. The truncation boundary is the only layer that cut the payload; every server-side layer passed it intentionally.Test Gap Analysis
open_kitchenresponse traversing the Codex code-mode exec harness — the four output-budget layers are each tested in isolation; the end-to-end "full recipe payload reaches the orchestrator on any backend in one call" invariant has no test.tests/recipe/test_bundled_recipes_all_truthy.pyfilters suggestions toseverity == "error"only, so warnings on bundled recipes pass silently and accumulate; no zero-warning or allowlist assertion exists forremediation.yaml.tests/server/test_tools_kitchen_gate_features.py:119-163,test_tools_kitchen_envelope.py); no sequential two-call workflow test exists (closest istest_tools_kitchen_cache_poison.py, which is cache-integrity framed).Similar Patterns
The
implementstep'son_context_limit: retry_worktreeroute (remediation.yaml:380,implementation.yaml:339) is the established pattern the warning rule asks for; recipe authors applied it where stranded writes are most costly (worktree mutation) and left plan/markdown-writing steps (rectify,dry_walkthrough,plan,verify) unrouted. For payload-size collisions, the established mitigations are per-path:ingredients_only(dispatch), deferred-recall stripping (Codex pre-reveal),RESPONSE_BACKSTOP_EXEMPTION_REGISTRY(Claude Code disk gate) — each fixed one boundary at a time.Design Intent Findings
ingredients_only: introduced ina2f066d39(2026-05-07, PR Fleet dispatch L3 session should auto-discover recipe ingredients without loading full recipe #1827, closes Fleet dispatch: L3 session should auto-discover recipe ingredients without loading full recipe #1803) to save ~16K tokens in fleet-dispatch L3 sessions that only need the ingredient schema. Callers are prompt-text instructions only (cli/_prompts_kitchen.py:42, 142); no Python caller passes it. [SUPPORTED]fbfb3589e(2026-06-12, PR [FIX] Double open_kitchen Context Cost — Pre-Reveal / Deferred-Recall Architectural Immunity #4092, closes Double open_kitchen context cost: recall re-sends full recipe; Codex amplifiers #4089 "Double open_kitchen Context Cost") — made the deferred-recall path honoringredients_only; the current incident's second call is this feature working as designed. [SUPPORTED]required-ingredient-no-default:a7ad04a48(2026-04-12, PR Rectify: Channel B Blind Spot + Pre-open_kitchen AskUserQuestion Deviation #750) — added after orchestrators rationalized calling AskUserQuestion before open_kitchen when a required ingredient had no default. [SUPPORTED]file-writing-skill-missing-context-limit:38c424d75(2026-04-14, PR Rectify: Context-Limit Dirty-Tree Architectural Immunity #901) — added after 9 confirmed dirty-tree incidents since 2026-03-16 where context exhaustion mid-edit stranded uncommitted changes. Its failure-mode claim matches the CONTEXT LIMIT ROUTING behavior in_prompts_orchestrator.py:176-235. [SUPPORTED]971685865/ADR-0005 (PR Output Budget Protocol Audit Remediation #4277, closes Codex investigate deep mode can consume 60K+ parent tokens from two tool outputs #4272) — formalized 4 budget layers and explicitly documented (Accepted Gap Feature: read_db MCP tool for read-only SQLite queries #2 and lines 108–111) that code-modemax_output_tokensis advisory-only. [SUPPORTED]Historical Context
Prior fixes found — this is the fourth manifestation of the same underlying weakness in ~2.5 months, each at a different boundary:
a2f066d39) — fleet dispatch paying full recipe cost →ingredients_only.fbfb3589e) — deferred-recall resending full content → stripping applied.1e2670ec7) — Claude Code ~100KB disk-persistence gate truncating open_kitchen to a 2KB preview → 186KB backstop exemption.ADR-0005 itself frames recipe-payload/boundary collisions as "the recurring failure class behind #4253, #2819, #2564, #3938."
This is a recurring pattern — consider running
/rectifyfor architectural immunity after resolving the immediate issue.An operational consequence worth noting: the Codex orchestrator in session
019f7fdbproceeded with only ~10K of 44K tokens of recipe context — most of the recipe body never reached the model (the orchestration rules, ingredients table, and deferred_guards survived in the truncated tail — by serialization-order luck, not by design), which still raises deviation risk for the rest of that pipeline run.External Research
Not applicable — all evidence was local (session rollout logs, source, git history, GitHub issues via
gh). Prior internal investigation of Codex context behavior (session12326fb2, 2026-07-20 06:48, re: Codex session019f6b5a) established that code-mode honors model-suppliedmax_output_tokenswithout an upper clamp — the unbounded intake failure direction. That does not conflict with this incident: here nomax_output_tokenswas supplied and the harness's default cap applied. Same root design (model-discretionary bound, no enforced config-level control), opposite symptom.Scope Boundary
Investigated: the Codex rollout log for the session in question; open_kitchen implementation and both response modes; the two semantic rules and their severities; remediation.yaml ground truth vs. other bundled recipes; orchestrator/kitchen prompt builders; introducing commits for all mechanisms; the four output-budget layers; GitHub issue history; test coverage.
Not yet explored: Codex-source confirmation of the exact default that produced the ~10K cut in this specific session (model-declared
max_output_tokensper the digest vs. a harness default — the digest rule matches the observed bound, but the model's encrypted reasoning prevents direct confirmation); whether other MCP tool responses in that session were similarly truncated; downstream behavior of session019f7fdbafter the user's task was provided.Recommendations
open_kitchenresponses exceed what the Codex code-mode exec harness will deliver, so the Codex interactive orchestrator can never receive the complete recipe payload in one call. The fix direction suggested by ADR-0005's own analysis: an enforced (config/harness-level) transport bound ormax_output_tokensfloor for MCP calls in code mode — analogous to the ADR-0006shell_capture_hookpattern — rather than the current prompt-level advisory digest; or per-backend serving that keeps the full-mode payload under the harness bound (e.g., a compact full-mode variant for Codex).required-ingredient-no-defaulton task-type ingredients as advisory noise (optionally exempt task-category ingredients from the rule, or accept the warning); thefile-writing-skill-missing-context-limitfindings are legitimate — if addressed, addon_context_limitroutes torectify/dry_walkthrough(andimplementation.yamlplan/verify) following the existingimplement → retry_worktreepattern, as a deliberate decision rather than warning suppression.Confidence Levels
"errors": []+ WARNING suggestions is normal, non-blocking output: SUPPORTEDon_context_limiton rectify/dry_walkthrough is real but systemic: SUPPORTEDmax_output_tokenson the exec calls; the ~10K cut is the harness default cap: SUPPORTED (verified against the full rollout log)errors/suggestions/diagramvalidity signals): NEEDS-EVIDENCE (inferred from the truncation warning; reasoning not inspectable)