Conversation
A file an exec command wrote reached the desk diff as a bare row: the runtime listed the working directory on either side of the command and knew a file had changed, never what it changed from. A rewrite showed a bare M with no counts, and opening any such row fell back to the file viewer instead of the diff pane. The checkpoint's shadow git repo now supplies the missing half. Just before each command the tree is staged into a per-process index of its own (never the index the turn commit reads), and afterwards the old contents of every rewritten or removed file are read back from that tree. Each file_written entry gains added/removed counts and a unified diff (created files are measured against nothing), and a listed removal carries the text it held. The page draws those rows with their hunks, so they open in the diff pane like a file tool's. - The first staging of a directory the shadow repo has never indexed hashes every file. It is started in the background on the directory's first turn in the process (warm returns at once; repo setup runs on the staging thread too), so it overlaps the model's first reply. - A command waits at most 6s for its staging. Past that it is not run: the call fails with a reply telling the model to run it again, and the staging carries on. A command whose tree cannot be staged at all (checkpoint off, outside the repo, git error) runs without a diff. - Stagings run on daemon threads with blocking git calls: an asyncio subprocess still starting when its loop closes hangs the close on CPython 3.12 macOS. add and write-tree run under one lock per index, and the add has its own 600s ceiling instead of the 30s one, which killed a cold staging of a large tree over and over. - The diffs share the event's 512 KiB budget; past it the counts still go and the diff is dropped whole. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
The warm-up runs the shadow repo's setup on its own thread, and a turn that ended before that setup did ran the same setup beside it. Two setups at once fail on the repo's config lock, and the one that lost was the turn's commit. The setup the warm-up started is now waited for, and repeated only if it did not succeed. The tests' wait for the staging thread moves from three workspace fixtures into one teardown hook in tests/conftest.py, ahead of every fixture finalizer: any test whose turn starts a warm-up can hit the same cleanup race. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
I reviewed the full github/main...HEAD diff and both branch commits, then traced the surrounding turn execution, checkpoint staging/readback, RPC persistence and replay, and web workspace rendering paths. I also checked the repository rules and canonical context terms/boundaries, backward compatibility of the optional wire fields and unmeasured fallback, relevant history, and whether existing tests were weakened. I tried to refute the concurrency and compatibility concerns raised by the design before concluding that none causes a concrete failure in this revision.
Verification:
uv run pytest tests/test_runtime_checkpoint.py tests/test_agent_loop_session_stamps.py -x- 68 passed afteruv sync --all-extras(the initial environment lacked the declaredraven_everosworkspace dependency)uv run pytest tests/test_rpc_schema_match.py tests/test_rpc_tool_events.py -x- 428 passednpm test -- --run src/features/workspace/record.test.ts- 38 passednpm run type-check- passednpm run gen:check- generated client matches the contractgit diff --check github/main...HEAD- passed
|
Not a blocker. These are the other things I verified myself on 1. The page throws away the counts the server computed, and the substitute can be wrong. That substitute loses lines. 2. 3. One correction to something you may hear from elsewhere. A cancelled staging future was reported to me as leaving an unhandled For completeness: the acceptance pass surfaced further non-blocking items I have not personally reproduced (two concurrent turns in one directory attributing each other's line counts; a binary file that happens to decode as UTF-8 receiving a diff of its bytes; coverage gaps where a mutation kills no test). I am deliberately not asserting those here, because I did not drive them myself. I will re-check them against whatever head answers the blocking thread. |
A command waited out any staging already running, then always started a fresh one inside the same budget. A staging near the budget refused most commands, and one past it refused every command for good: each retry threw away the staging the last one had waited for and started another equally slow one. The latest staging is now reused whenever it started after the last write into the directory. note_write marks one after every tool call and at the start of every turn, so a file the user saved between two messages is never shown as a command's change. A retry waits for the staging the refused call left running, and a warm-up is the first command's baseline, so each command costs one staging and a staging of any length is waited out by retries. A held-back command is now logged. A staging's future is marked running as it is created. The warm-up sets the repo up before it stages, and a waiter that timed out in that window cancelled it, which raised InvalidStateError on its thread and handed every later reuser a CancelledError. Review follow-ups: the page takes the runtime's added/removed counts over ones re-read from the patch, and fromUnified skips only a real ---/+++ header pair, not every line that starts that way (a removed "-- note" line was dropped, row and count both). The dead _stage_index is gone, with its rationale moved to _stage_path, and the _GIT_TIMEOUT_SECONDS comment no longer claims every git call. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
|
Thanks, all three taken in 5150f0a:
On the cancelled-future note: it was reachable after all, on the warm-up path (the repo setup runs before the staging marks its future running). Details and the fix are in the checkpoint.py thread. 🤖 Addressed by Claude Code |
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: the new timing test must satisfy the repository's strict CI idle ceiling.
The runtime blocker from the prior revision is fixed: the unchanged 6.25s staging reproduction now times out once at 6.01s and returns the staged tree immediately on retry. I also rechecked the revision delta, checkpoint callers and concurrency history, the AGENTS/CONTEXT architecture constraints, optional-wire backward compatibility, the three UI follow-ups, and whether tests were weakened; I found no other concrete failure.
This late-round blocker meets all three required conditions: this revision introduces the unmarked timing test, every ordinary CI shard runs with --idle-ceiling-strict, and the session fails with no merge path until the test is marked appropriately or its intentional wait is shortened.
Verification:
- original 6.25s staging probe:
timeoutthentreeon retry uv run pytest tests/test_runtime_checkpoint.py tests/test_agent_loop_session_stamps.py -x: 74 passed, with the idle-ceiling warninguv run pytest tests/test_runtime_checkpoint.py::test_a_staging_under_the_budget_is_never_refused --idle-ceiling-strict: assertion passed but the session failed on 3.20s unmarked idle timenpm test -- --run src/features/workspace/record.test.ts src/lib/hunks.test.ts: 52 passednpm run type-check: passednpm run gen:check: generated client matches the contractgit diff --check github/main...HEAD: passed
The four tests that slow a staging to sit either side of the wait budget wait by design, and one of them crossed the suite's 3s idle ceiling on CI. They prove a timing property, which is what production_timing exempts from the ceiling. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
The only revision delta adds the repository-defined production_timing marker, with reasons, to the four tests whose intentional staging delays are the property under test. This clears my CI blocker without changing production behavior or weakening assertions. I rechecked the delta against the prior full review, repository test rules, relevant marker implementation, and the standing discussion record; no unresolved item remains from my side. The original runtime thread belongs to 0xKT and remains theirs to resolve.
Verification:
uv run pytest tests/test_runtime_checkpoint.py tests/test_agent_loop_session_stamps.py -x --idle-ceiling-strict: 74 passedgit diff --check github/main...HEAD: passed
|
Not a blocker -- a correction to myself. I was wrong about the cancelled-future crash, and I was wrong in the worst direction: I told you not to spend time on it. You were right that it was reachable, and the path you name is the one. I have now reproduced it on And the same probe on Why my first probe could not have found it, which is the part worth recording. I slowed So my negative rested on a probe that never entered the window, and I never checked that it could. A negative result needs a positive control -- proof that the probe can produce the thing under some conditions -- before it is worth anything, and I published one without. The fix is verified from my side: moving Sorry for the detour I sent you on. |
|
Not a blocker. Graded A at 1. Half of the counts fix is unpinned.
So a later edit could put the derived number back on the merge path and the suite would stay green. That path is where a command that writes the same file more than once accumulates its counts, which is where a wrong number is least likely to be noticed by eye. 2. Two things I am NOT reporting, having checked them myself. A staging is not re-started per command, and a write that lands during the model's reply is therefore absent from the next command's baseline. I reproduced that: with nothing calling A warm-up whose repo setup fails caches a Gates, all run by me on this head: |
…mand is staged The page adds a command's change to a row the turn already has using the runtime's added/removed counts, and nothing failed when that branch went back to the patch's own. A test now pins it. CONTEXT.md still said stage_tree stages the tree again before each command. Since stagings are reused, a command takes the latest one when it began after the last note_write and stages afresh only otherwise. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
|
Both taken in ed71c7a:
Thanks also for writing down the two you checked and set aside. 🤖 Addressed by Claude Code |
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
I reviewed the full revision delta against the prior accepted head, the workspace recorder implementation and callers, the checkpoint context contract, repository rules, backward compatibility, and whether the added test weakens or merely pins behavior. The new test correctly covers runtime-count preference when a command's measured change is merged into an existing row, and the context text now matches the implemented staging reuse/invalidation model. No production code changed and no prior settled issue regressed.
Verification:
npm test -- --run src/features/workspace/record.test.ts: 40 passednpm run type-check: passedgit diff --check github/main...HEAD: passed
|
Not a blocker. Graded A at The merge branch is pinned now. Last round, reverting
Gates on this head, all mine: For the record of this PR as a whole: one blocking finding, verified fixed and closed; one false negative of mine, corrected; three non-blocking items, all answered. Nothing is outstanding from my side. |
|
Superseded by #826. Review here found real defects in the short-budget, refuse-and-retry and staging-reuse machinery; #826 drops it for a simpler model: every exec stages the directory afresh inside its own call and waits up to 120s for it, with a warm-up started as the session opens. Thanks @0xKT and @gloryfromca for the reviews that drove the change. |
Summary
A file an
execcommand wrote reached the desk diff as a bare row. The runtime listed the working directory on either side of the command (#692), so it knew a file had changed but never what it changed from: a rewrite showed a bareMwith no counts, and opening any such row fell back to the file viewer instead of the diff pane. File-tool edits were unaffected; this only concerned commands (python3 gen.py > out.json,echo x >> notes.md,sed -i ...).The checkpoint's shadow git repo now supplies the missing half:
CheckpointService.stage_treestages the working tree into a per-process index of its own (never the index the per-turn commit reads). After the command,read_blobsreads back the old contents of every rewritten or removed file.file_writtenentries gainadded/removedcounts and a unifieddiff(created files are measured against nothing). A removal the listing found now carries the text it held. Wire contract updated inrpc-schema/openrpc.json,raven/rpc/models.pyand both generated clients.listedrather than inferred from "has no hunk".Cost and waiting (decided in review with the maintainer):
warmreturns at once; the repo setup runs on the staging thread too), so it overlaps the model's first reply. Later stagings are a stat walk (about 100-300 ms).working_diroutside the repo, git error) runs without a diff, as before.addandwrite-treerun under one lock per index. Theaddhas its own 600 s ceiling: under the shared 30 s one a cold staging of a large tree was killed and restarted forever, and every command was held back.Not covered: files the shadow repo excludes (
*.log,.env, the user's.gitignore), text files over 256 KB, sub-agent lanes (#670 run records are unchanged), and existing sessions (no old contents were ever stored). Steering the model toward file tools in theexecdescription is left out because trajectory cassettes pin that description.Type
Verification
make test-python-- 27040 passed, 108 skipped, 3 failed. The 3 failures (test_rpc_files.py::test_a_host_without_libreoffice_says_so,test_simulation_scenario.py::test_a_fresh_trial_refreshes_the_subagent_homes_before_it_runs,test_simulation_suite.py::test_a_stopped_run_is_marked_in_its_record_and_finished_with_its_evidence) fail the same way ongithub/mainon this machine.uv run --frozen --all-extras pytest tests/test_agent_loop_workdir.py tests/test_runtime_checkpoint.py tests/test_runtime_checkpoint_deep.py tests/test_agent_loop_session_stamps.py tests/test_rpc_message_tool_route.py -q-- 126 passed, three runs in a row, and five runs under added load for the timing-sensitive tests.make lint-python lint-types lint-imports-- clean;npm run gen:check --prefix ui-web,npm run type-check --prefix ui-web,npm run lint --prefix ui-web(0 errors, 4 pre-existing warnings),npm run lint:rpc --prefix ui-tui-- clean.npm test --prefix ui-web-- 3104 passed (including the fixture-shape gate);npm test --prefix ui-tui-- 2111 passed.warm; the setup run before waiting out the warm-up; the commit's setup not waiting for the warm-up's; the index not seeded; the stale lock kept; the 30 s ceiling on the staging add; removals ignoring the old contents; created files without a diff; the diff budget ignored; the counts dropped; the page ignoring the diff, the counts, or thelistedflag; a measured change not appended to an existing row.raven servefrom this branch, isolatedRAVEN_HOME, real model), exec-only commands:A demo.txt +3,M demo.txt +2 -1andM readme.md +1(a file Raven had never touched before). Each opens in the diff pane, and a reload draws the same rows. After a restart, the first command in the new process is measured too.Risk
User-visible changes:
execand an active checkpoint may start one backgroundgit addper directory per process, writing into the same.raven/shadow.gitthe checkpoint already uses. Each process keeps its ownexec-<pid>.indexfile there; files older than 7 days are pruned..env, the user's.gitignore).FileWrittenare optional, so older clients ignore them.Rollback: revert the two commits. The staging indexes in
.raven/shadow.gitare inert without the code and are pruned by age.Related Issues
N/A