Skip to content

feat: replace output_budget_guard with lossless shell capture (#4286)#4288

Merged
Trecek merged 19 commits into
developfrom
impl-output-boundary-bounding-20260718-113106
Jul 19, 2026
Merged

feat: replace output_budget_guard with lossless shell capture (#4286)#4288
Trecek merged 19 commits into
developfrom
impl-output-boundary-bounding-20260718-113106

Conversation

@Trecek

@Trecek Trecek commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Phase A: Scope output_budget_guard to Codex-only with typed provenance messages; Claude Code sessions no longer see deny surfaces
  • Phase B: Lossless capture promotion for run_cmd — subprocess output goes directly to artifact-directory files with bounded inline slices and fail-stop capture semantics
  • Phase C: Create shell_capture_hook.py (PreToolUse input-rewrite) that wraps every Codex native shell command in a capture harness with trap-EXIT postlude, delete the command-shape classifier and output_budget_guard.py entirely

Implements ADR-0006 (Output-Boundary Containment). The pre-execution shape classifier is retired in favor of per-backend output-boundary bounding on measured bytes.

Key changes

  • New shell_capture_hook.py — every Codex shell command's complete output captured to .autoskillit/temp/shell_capture/shell_<uuid8>.log; bounded inline slice with provenance marker
  • classify_command_output_budget and ~380 lines of classification machinery deleted from _command_classification.py
  • small_file_max_bytes removed from config surface (existed solely for classifier's JSONL exception)
  • 13-row semantic conformance corpus + 6 adversarial-edge rows as hard gate
  • 5 new capture-mode error-path tests (CaptureReadError, timeout, signal death, orphan prevention)

Closes #4286

Test plan

  • task test-check passes (29,718 passed, 553 skipped, 55 xfailed)
  • pre-commit run --all-files clean
  • Conformance corpus: wrapped == raw for exit codes and captured bytes across 19 corpus rows
  • test_capture_dir_uncreatable_fail_stops: fail-stop verified
  • All existing hook tests updated for new registry entry
  • ADR-0005 Layer 3 retired, Gap 1 updated; ADR-0006 completed with accepted gaps

🤖 Generated with Claude Code

@Trecek Trecek left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AutoSkillit PR Review — Verdict: changes_requested

Comment thread src/autoskillit/execution/process/_process_io.py Outdated
Comment thread src/autoskillit/hooks/shell_capture_hook.py
Comment thread src/autoskillit/hooks/shell_capture_hook.py
Comment thread src/autoskillit/server/tools/_execution_helpers.py Outdated
Comment thread src/autoskillit/server/tools/tools_execution.py Outdated
Comment thread src/autoskillit/server/tools/tools_execution.py Outdated
Comment thread tests/contracts/test_protocol_satisfaction.py
Comment thread tests/hooks/test_shell_capture_conformance.py
Comment thread tests/server/test_tools_execution_spill.py

@Trecek Trecek left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AutoSkillit review: blocking findings detected. Changes are requested; see inline comments.

@Trecek Trecek left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AutoSkillit PR Review — Verdict: approved_with_comments

Comment thread src/autoskillit/server/tools/_execution_helpers.py Outdated
Comment thread src/autoskillit/server/tools/_execution_helpers.py Outdated
Comment thread tests/hooks/test_policy_event.py Outdated
Comment thread tests/hooks/test_policy_event.py Outdated
Comment thread src/autoskillit/server/tools/_execution_helpers.py
Trecek and others added 18 commits July 18, 2026 15:46
… Phase A)

- Add _policy_event.py: typed PolicyEvent dataclass + render_provenance_prefix
- Gate output_budget_guard on Codex sessions (env var OR turn_id in payload)
- Replace R1-R3 jargon with provenance-bearing deny messages
- Add _suggest_bounded_rewrite: classifier-validated rewrite suggestions
- Update HookDef enforcement_strength to not-applicable for claude_code
- Create ADR-0006 (output-boundary containment), amend ADR-0005 Layer 3
- Update all dependent tests for new Codex-only scope and message format

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cture)

- Add CapturedStream dataclass to core types
- Add CaptureSetupError/CaptureReadError exceptions and summarize_capture()
- Extend create_temp_io with capture_dir and keep_streams parameters
- Thread capture_dir through SubprocessRunner protocol and all conformers
- Add _run_subprocess_captured for capture-mode subprocess execution
- Extract run_cmd_artifact_root helper from spill_run_cmd_result
- Rewrite spill_run_cmd_result to accept CapturedStream objects
- Update run_cmd to use capture mode with exhaustive TerminationReason handling

Tests not yet updated — next commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Re-export CaptureReadError/CaptureSetupError/summarize_capture through facade
- Update tools_execution.py imports to use facade (not private _process_io)
- Extract _summarize_streams helper to satisfy no-for-loop-in-handlers rule
- Update MockSubprocessRunner to handle capture_dir in tests/fakes.py
- Update spill/run_cmd tests for capture-mode SubprocessResult
- Add test_process_io_capture.py and TestCaptureMode integration tests
- Fix arch exemptions: import allowlists, cross-submodule exemptions, pyright budget
- Regenerate codex hook format snapshot fixture
- Rename ADR-0006 to comply with 3-segment filename rule

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…4286)

Replace output_budget_guard with shell_capture_hook (PreToolUse input-rewrite).
Delete classification machinery, remove small_file_max_bytes config surface.
Conformance corpus (13 rows) as hard gate. ADR-0005/0006 updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add 5 new tests to test_tools_execution_spill.py covering capture-mode error
paths (CaptureReadError, CaptureSetupError, timeout, signal death, orphan
prevention). Add 6 conformance corpus rows (rg, jq, heredoc normalization,
trailing backslash, self-signal, unicode boundary) to satisfy OBB-038/OBB-041.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ables

The printf marker argument was single-quoted, suppressing expansion of
$__as_sz, $__as_f, and $__as_sha. Use POSIX quote-break technique
('literal'"$var"'literal') to allow variable expansion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OSError from NamedTemporaryFile (disk/fd exhaustion, permission race)
now raises CaptureSetupError instead of bypassing the structured
capture-failure handler. Cleans up stdout file if stderr creation fails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Non-dict JSON (null, scalar, list) would crash _is_codex_session or
.get() calls with TypeError/AttributeError instead of graceful exit(0).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
os.open() returns a fd that was passed to fsync() but never closed,
leaking one descriptor per promoted capture stream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expose CaptureReadError, CaptureSetupError, and summarize_capture from
autoskillit.execution instead of requiring direct submodule import.
Remove the bespoke REQ-ARCH-001 exemption that weakened the guard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When summarize_capture fails, the proc_*.tmp file was left on disk with
no reference in the output. Now log the path in the error message and
unlink the file to prevent accumulation of undiscoverable orphans.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover stream retention, path population, and setup-error handling for
the synchronous capture_dir path, matching the async test coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The existing oracle concatenates separately captured stdout+stderr
which does not match real 2>&1 interleaving. Add _run_raw_merged helper
and a test that alternates both descriptors to validate ordering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The existing mock-based test bypasses the streaming path. Add a
complementary test that verifies summarize_capture returns bounded
slices from a real 200KB file without reading it fully into memory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test previously asserted no double quotes in the marker, which
conflicts with the fix for variable expansion. Now validates that each
shell variable is wrapped in expansion-safe '"$var"' pattern instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ailure

Moves the function-local os/uuid imports in _process_capture_stream and
_uuid8 to module scope (os was already imported), and wraps os.replace
in try/except OSError so a failed artifact promotion returns a
structured capture_failed error instead of crashing the tool call.
test_policy_event.py exercised render_provenance_prefix() using the
retired output_budget_guard hook name and its old reason code. Update
both cases to shell_capture_hook v1 / SHELL_OUTPUT_CAPTURED, matching
what the production hook actually emits.
test_run_cmd_partial_capture_error_leaves_no_orphan only checked the
successful stdout capture file was unlinked, despite its name implying
full orphan coverage. Add the matching assertion for the stderr file
that failed summarize_capture().
@Trecek
Trecek force-pushed the impl-output-boundary-bounding-20260718-113106 branch from e9be65d to fbb79bd Compare July 18, 2026 22:46
…e limit

tools_execution.py exceeded the 1600-line exemption after rebase.
Move _summarize_streams (and its CaptureReadError/summarize_capture deps)
to _execution_helpers.py where _process_capture_stream already lives.
Update test monkeypatch targets accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Trecek
Trecek added this pull request to the merge queue Jul 19, 2026
Merged via the queue into develop with commit 98ef9bd Jul 19, 2026
3 checks passed
@Trecek
Trecek deleted the impl-output-boundary-bounding-20260718-113106 branch July 19, 2026 00:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant