perf(prompt): let throwaway scripts be authored inline in Bash - #753
Merged
Merged
Conversation
Iteration 4 on the trajectory gap against the latest Claude Code, measured
with eval/harbor/compare_trajectories.py on seven terminal-bench 2.1 tasks
at the same model (claude-opus-5), effort (xhigh) and timeout multiplier.
The change: the "To create files use Write instead of cat with heredoc or
echo" sub-bullet now adds that a throwaway script you run once and discard
is part of the command rather than a deliverable, and MAY instead be
authored inline in the same Bash call that runs it. The Write preference is
untouched for deliverables -- that is what keeps real edits reviewable as
diffs. "Reserve using the Bash EXCLUSIVELY for system commands" three
lines below loses "exclusively", which cannot coexist with the carve-out.
Result: behavior converged, step count improved modestly
--------------------------------------------------------------------
Against the prior clean state (iter2), on the six tasks both runs
completed:
mean steps 10.67 -> 10.00 (Claude Code 6.50)
gap vs CC +4.17 -> +3.50 (16% of the gap closed)
per task 3 improved, 2 regressed, 1 unchanged
regex-log 15 -> 7 (Claude Code 7 -- exact match)
distribution-search 10 -> 6
cancel-async-tasks 9 -> 6
sparql-university 12 -> 12
fix-code-vulnerability 10 -> 12
circuit-fibsqrt 8 -> 17 <-- most of the remaining drag
Reward: 7/7 at 1.0, none killed. iter2 had one trial killed at its
timeout; the withdrawn iter3 had one outright FAIL.
The behavior metrics moved further than the mean:
tool mix Bash/Write 70.2/19.1 -> 78.8/11.8 (CC 79.7/11.9)
heredoc-authored 12.1% -> 20.9% (CC 23.4%)
exact-repeat rate 5.3% -> 2.4% (CC 0.0%)
Bash stmts median/mean 3.0/4.4 -> 4.0/5.2 (CC 3.0/5.5)
Tool mix is now within a point of Claude Code's on every entry.
What is NOT claimed
--------------------------------------------------------------------
The mechanism I originally wrote down was wrong, and review caught it.
I had claimed clawcodex was effectively barred from authoring scratch
files in Bash, citing a 2.7% heredoc rate. That figure came from the
WITHDRAWN iter3 build and, worse, counted heredoc syntax only. Counting
any Bash call that authors file content (heredoc or printf/echo/tee
redirect), clawcodex was already near parity before this change: iter0
27.7%, iter2 24.2%, Claude Code 27.7%. Claude Code also uses Write for
probe scripts -- 294 of its 327 Write/Edit calls target non-/tmp paths.
So the improvement above is measured, but it is not explained by "the
prompt forbade something Claude Code does". Most likely the carve-out
removes hesitation at the margin rather than unlocking a blocked
behavior. Recording that honestly matters more than a tidy story:
iterations 2 and 3 both had tidy stories and delivered nothing and harm
respectively.
Known cost, accepted deliberately
--------------------------------------------------------------------
In acceptEdits mode Write is auto-accepted while any Bash redirect
prompts, so taking this option trades a saved step for an approval prompt
there. Verified: check_accept_edits_bash
(src/permissions/bash_mode_validation.py:304) gates on
_has_shell_redirection, NOT on the redirect target -- `cat` is in
ACCEPT_EDITS_READ_ONLY_COMMANDS whose branch requires
`not redirection_anywhere` -- so `cat > /app/x <<PY`, `cat > /tmp/x <<PY`,
`cat > ./x <<PY` and `printf 'x' > /app/x` all return False and prompt.
Location therefore makes no permission difference and none is specified.
Hence "may", not an instruction: the model keeps Write when a prompt would
cost more than the step saves. No approval is bypassed -- this adds
prompts, never skips them.
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.
Iteration 4 on the trajectory gap against the latest Claude Code, measured with
eval/harbor/compare_trajectories.pyon seven terminal-bench 2.1 tasks at the same model (claude-opus-5), effort (xhigh) and timeout multiplier.The change: the "To create files use Write instead of cat with heredoc or echo" sub-bullet now adds that a throwaway script you run once and discard is part of the command rather than a deliverable, and may instead be authored inline in the same Bash call that runs it. The Write preference is untouched for deliverables — that's what keeps real edits reviewable as diffs. "Reserve using the Bash exclusively for system commands" three lines below loses "exclusively", which cannot coexist with the carve-out.
Result
Against the prior clean state (iter2), on the six tasks both runs completed:
Gap +4.17 → +3.50 (16% closed). 3 improved, 2 regressed, 1 unchanged.
regex-logmatches Claude Code exactly;circuit-fibsqrtis most of the remaining drag and is iteration 5's target.Reward: 7/7 at 1.0, none killed. iter2 had one trial killed at its timeout; the withdrawn iter3 had one outright FAIL.
The behavior metrics moved further than the mean:
Tool mix is now within a point of Claude Code's on every entry.
What is NOT claimed
The mechanism I originally wrote down was wrong, and review caught it. I'd claimed clawcodex was effectively barred from authoring scratch files in Bash, citing a 2.7% heredoc rate. That figure came from the withdrawn iter3 build and counted heredoc syntax only. Counting any Bash call that authors file content (heredoc or
printf/echo/teeredirect), clawcodex was already near parity beforehand: iter0 27.7%, iter2 24.2%, Claude Code 27.7%. Claude Code also usesWritefor probe scripts — 294 of its 327 Write/Edit calls target non-/tmppaths.So the improvement is measured, but it is not explained by "the prompt forbade something Claude Code does." Most likely the carve-out removes hesitation at the margin rather than unlocking a blocked behavior. Recording that honestly matters more than a tidy story: iterations 2 and 3 both had tidy stories and delivered nothing and harm respectively.
Known cost, accepted deliberately
In
acceptEditsmodeWriteis auto-accepted while any Bash redirect prompts, so taking this option trades a saved step for an approval prompt there. Verified:check_accept_edits_bash(src/permissions/bash_mode_validation.py:304) gates on_has_shell_redirection, not on the redirect target —catis inACCEPT_EDITS_READ_ONLY_COMMANDSwhose branch requiresnot redirection_anywhere— socat > /app/x <<PY,cat > /tmp/x <<PY,cat > ./x <<PYandprintf 'x' > /app/xall returnFalseand prompt. Location therefore makes no permission difference and none is specified. Hence "may", not an instruction. No approval is bypassed — this adds prompts, never skips them.Tests
8840 passed, 3 skipped, 0 failures.
Two same-named
test_connection_error_re_raisestests are deselected —tests/test_stream_watchdog.py(known) andtests/test_agent_loop_compat_model_error.py::TestModelErrorPropagation(found this session). The latter hangs insidesrc/tasks/eviction.py:205 _sweeper_loop, passes in isolation, and is byte-identical onmain, so it is a pre-existing background-sweeper test-interaction flake, not a regression. Located by dumping the stack withSIGABRTafter--timeout-method=threadfailed to interrupt it.🤖 Generated with Claude Code