Repository navigation
fix(O128,O122): partial-read edit gate holds on retry; full re-read registers; truncated write refused; resolvable gate blocks - #127
Merged
Conversation
…r answered from replay cache - O97 attached body no longer stamps a full read (tool-helpers partialReadRecovery); loop-guard locks on every refusal - every partial-read refusal ends with the same next step (en+zh) - read_file metadata.noReplay: loop replay cache skipped so a full re-read registers (O122) - process test with 48k-char file + small-file QA repro; O97 unit tests tightened
…gets; credential + noReplay-scope + exec-bypass tests
- shell-write-detect: expand $PWD/${PWD}/$HOME before resolving; run-time-built targets match a locked path by literal suffix (only adds blocks)
- findLoopReplay exported: noReplay only on read_file, other read-only builtins still replay (test)
- process tests: sed -i, cat > ./x, ../dir/x, tee, python, symlink alias, $PWD, $D, $(pwd), backtick all blocked after partial-read refusal
- credential files (.moss/config.json, .env, .apikey-key, *.credentials): no body on refusal or any resend
…as truncated (PM ruling); edits unchanged
…lvable by realpath via prescribed tools; exec never resolves
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
6 tasks done
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.
O128 (P1 safety) + O122: partial-read edit gate holds on retry; full re-read registers
Root cause
partialReadRecovery,src/tools/tool-helpers.ts) attached the file body to the refusal and then calledrecordFileState(…'full'). The refusal therefore counted as a full read, and the identical resend was written. The loop guard (tool-loop-guard.tstrackReadScopeLocks) had a matching exception: a refusal with an attached body didn't lock the path, so shell writes to it went through too.read_fileis read-only, so an identical full re-read was answered from the loop's replay cache (agent-loop-tool-execution.ts~166-184).execute()never ran and the full read was never registered.Changes (gate only gets stricter)
x再编辑」 / "Next step: readxin full first, then edit".read_filegetsmetadata.noReplay, so it always executes. Onlyread_filehas it (tested); the other 22 read-only tools still replay. Its own "unchanged" short reply keeps repeat reads cheap: 48k file, full→full went from 55,994 B to 209 B.$PWD/${PWD}/$HOMEare expanded, and targets built at run time ($D/x,$(pwd)/x,`pwd`/x) are matched against locked paths by their literal suffix.read_fileof a file was truncated by the output limit,write_filecan't overwrite that whole file (「x读取时被截断,不能整文件覆盖,请改用 edit 修改」 / "xwas truncated when read; whole-file overwrite is not allowed, use edit instead"). edit/multi_edit/apply_patch are unchanged. The block stays in place after edits and is cleared only by a full read that wasn't truncated.write_file) are recorded as resolvable with a realpath key. A later successful change to the same file through the prescribed tools resolves the block and the run exits 0. For partial-read those tools are edit_file/multi_edit/apply_patch/write_file, which can only succeed after a full read. For truncated they are edit_file/multi_edit/apply_patch. If the block is still unresolved at the end, the last line says 「被拦:局部读后编辑x」/「被拦:截断读后整文件覆盖x」 and the run exits 9. An exec write never resolves (and fix(safety): O103 pre-block dangling symlinks, curl/wget output, git core.hooksPath #115's "modified" logic stays: exit 9). Safety-type refusals (dangerous command, protected path, hook, -p auto-deny) are not resolvable. The old partial-read clearing compared raw path strings and also cleared onmove_file/delete_file; both are tightened.Tests (process-level ones use the real
dist/cli.js -pwith a fake model server; zh+en)test/o128-partial-read-retry.spec.mjs: identical resend, changed parameters, multi_edit, apply_patch, write_file and a small-file resend are all refused (exit 9, 被拦). After a real full read, the first edit succeeds: the O106 run2 sequence, the O122 repeated full read, and a small file. A truncated full read refuses write_file, the next edit succeeds right away, and write_file is still refused after it. An untruncated full read allows write_file.test/o128-gate-resolve.spec.mjs(zh+en, 14/14): partial-read refuse→full read→edit = 0 (also via./notes.txt, realpath); refuse→finish = 9; refuse→exec write = 9. Truncated refuse→edit = 0; refuse→finish = 9; refuse→exec write = 9.test/o128-exec-bypass.spec.mjs: sed -i,cat > ./x,../dir/x, tee, python,$PWD,$D,$(pwd), backticks and symlink aliases are all blocked.test/o128-no-replay-scope.spec.mjs, plus a credential-file test:.moss/config.json,.env,.apikey-keyand*.credentialsget no body on the refusal or on any resend.Tightened existing tests (O128) — each only got stricter
222171d2)test/partial-read-recovery.spec.mjs— "after the rejection the next edit succeeds and state is a full read" → renamed "O128: the attached body does not unlock — resend refused until a real full read"freshfull → full re-read got the unchanged stub → resend edit succeededmiss→ identical resend refused (same message + same next step, file byte-identical) → real fullread_filereturns the body (not the stub) → edit succeedsfresh/stub assertions were replaced by strictermiss/real-body ones. Nothing went from reject to allow.test/partial-read-recovery.spec.mjs— "write_file after partial read: rejected with content, then the write succeeds"read_file→ write succeedstest/partial-read-recovery.spec.mjs— "~20k-char file: model receives the full body…" (final step)read_file, then the edit succeedstest/exec-write-lock.spec.mjs— "refusal that attached the full file does not lock" → "O128: refusal that attached the full file still locks until a real full read"echo y > src/big.tsvia exec was allowed (null)shell write to edit-locked file); a fullread_fileunlocks itThe first three rows are the three tightened O97 tests in
partial-read-recovery; the fourth row is the matching loop-guard lock. No assertion was deleted. Each changed expectation turned an allow into a reject (and the old allow now happens after a real full read). Revert check: reverting the fix makes all four red.Known limitations
python3 -c "open(os.getcwd()+'/x','w')",node -ewithpath.join(process.cwd(), …)) can't be caught by static parsing. The shell write lock covers literal paths,$PWD/$HOME,$VAR/$(…)/backtick suffixes and symlink aliases. The remaining backstop is the L3 snapshot (workspace snapshot/rollback).Needs QA independent safety review (src/safety changed).