Repository navigation
fix(agents): clear the first-run flag when the catch-up completes (#122) - #232
bniladridas wants to merge 2 commits into
Conversation
Vycari ReviewRound 5 on 3dd17ca. The verdict is the Vycari Review check run, and each open finding is an inline thread. Reply on every P0 and P1 thread: Open: P0 0 · P1 0 · P2 0 · disputed 0 · confirmed fixed 0 · withdrawn 0 Hand-off: Greptile requested on 3dd17ca. No open findings this round. P2s, judge's notes and closed findingsJudge's notes No candidate findings were raised. |
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
37df245 to
a50b391
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a50b391031
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Replay completion needs a contract decisionRound 3 raises two P1s and one P2, and they share one cause: the current patch uses a cursor frame as both a skip/resume marker and an end-of-replay marker. The client contract does not support the second use.
I would separate the work into three independent concerns:
Candidate client-side changes, pending contract confirmation:
These are candidate changes, not a complete fix. None of them solves Two contract questions for the maintainer:
I have not changed code or resolved the three threads. #232 stays open. I will keep the two P1 threads open until the contract question is settled. |
|
Thanks for digging into this so carefully. Your contract questions were exactly the right ones to ask. Answers:
Given that, could you keep commit 1 (moving the Generated by Claude Code |
The flag says this process began with no earlier position, so an unrecognised event older than the start cutoff was replayed history rather than something missed. Only the backlog push cleared it, and that returns early when nothing was skipped, so a quiet first run kept the flag for the life of the process. After a reconnect, the replay can hold an event from before the session, which was missed and owes a resync. Left set, the flag took it for history and dropped it without a trace, and nothing told the agent it had lost a section of the stream. Clear it in the catch-up that ends the replay, whether or not anything was skipped, so the flag describes the first replay and no later one. Signed-off-by: Niladri Das <125604915+bniladridas@users.noreply.github.com>
States the agent event stream contract in FeedFrame and the channel plan, from the contract questions on Vycari#232. Refs Vycari#239. Claude-Session: https://claude.ai/code/session_01QNDehAAVazXavfSCUqc9om Co-authored-by: Claude <noreply@anthropic.com>
a50b391 to
b0cb937
Compare
|
@greptileai review |
The channel treated every comment except connected as proof the replay was over. The contract defines two: connected opens a connection, and a ping is sent only while the server has nothing past the caller's position, so the ping is the replay boundary. Any other comment, a proxy keepalive or a frame from a newer server, says neither. The early flush matters most on a first run: an unrelated comment cleared first_run before the replay ended, so an old event replayed after it owed a spurious resync and the backlog lost its first-run wording. The new test plays a keepalive before the drop and an old renamed event after the reconnect, and fails on the old check. Refs Vycari#122 Signed-off-by: Niladri Das <125604915+bniladridas@users.noreply.github.com>
|
@greptileai review |
Human overview
A quiet first run could keep its first-run state set because the clear only happened when a backlog was pushed. On reconnect, a missed old event could then be mistaken for replayed history and dropped. This clears the flag when the catch-up completes, whether or not anything was skipped. Refs #122 (item 2 of 4).
AI reviewer
Details for agents: what the diff does, invariants, verification, checklist
What the diff does. Two commits on top of
8b6ce9b, both insrc/pepper_cli/claude_channel.pyandtests/test_claude_channel.py(+44/-2 across the two files; the source adds one comment block and changes one condition).b0cb937movesself.first_run = Falseout of_flush_backlog, where it sat after the early return for an empty backlog, and into_caught_up, which runs when the replay boundary is reached.3dd17cachanges the comment branch of_handle_frame: the catch-up now ends only when the comment text is exactlyping, where the old check ended it for every comment exceptconnected. Two tests pin the work.test_an_old_event_replayed_after_a_first_run_still_owes_a_resynccovers a quiet first run: connect, receive the scope and a ping, drop, reconnect with an old unknown event and a ping, and assert oneresyncbecause the first ping clearedfirst_run.test_a_non_ping_comment_does_not_end_the_first_run_catch_upcovers the comment boundary: a skipped old message and: keepalivebefore a drop, then an oldgroup.renamedand a real ping after the reconnect, asserting one first-runbacklogand noresync.Invariants. For
--start beginning, only the catch-up ends first-run suppression; the head-start path ends it in_live_edgewhen the stream reaches the live edge. Only a: pingis a catch-up boundary: the server sends one only while it has nothing past the caller's position, the contract recorded in #250.: connectedopens a connection, and any other comment, including a proxy keepalive or a frame from a newer server, establishes neither. Acursorframe is still a skip marker that advances the position and never ends the replay. On the beginning-replay path,first_runnow means the process has not completed its first replay, not that a backlog was pushed._flush_backlogstill clears the skipped-message state,_live_edgestill clearsfirst_runfor a head start, and_is_first_run_historyis unchanged. The catch-up path is the single place that ends first-run suppression for--start beginning; it also flushes any owedbacklogorresyncnotice and persists the cursor.Alternatives rejected. Leaving the clear in
_flush_backlogleaks the flag on a quiet first run. Treating any comment exceptconnectedas the boundary, which is the old behavior, lets an unrelated comment end the replay early: it clearsfirst_run, flushes the backlog mid-replay, and makes an old event replayed after it owe a spuriousresync. Addingreplay_pending,_end_replay, a cursor-boundary rule, or a ping-before-head guard was dropped after the maintainer confirmed that a ping means caught up, thatstart=headopens with the head cursor before any event, and that a cursor frame is only a skip marker. The docs half of that decision landed separately in #250 (b4d1693).Failure posture. Ending the catch-up too early can produce a spurious
resyncand the wrong backlog wording, and it can drop an old event without a trace; ending it only on a ping makes an unrelated comment harmless. Failing to end it at all can silently drop a missed old event on reconnect, which is the bug inb0cb937. If a server never sends a ping after the replay, the catch-up stays open, which is the existing behavior for a quiet stream; the contract sends a ping about every 15 seconds while idle, and this PR adds no timer of its own.Where to look hardest. The comment branch of
_handle_framemust compare exactly withpingand must still ignoreconnectedwithout ending the catch-up. The head-start live-edge path uses thescopeandcursorframes, not comments, so it is untouched; the new boundary test covers--start beginning, and the existing head-start tests cover the rest. The new test fails on the old!= "connected"check (1 failedlocally) and passes with the exact comparison. Also check that no other path clearsfirst_runfor a--start beginningreplay.Verification. macOS, Python 3.13 via
uv, on3dd17ca:uv run python -m pytest tests/test_claude_channel.py -q:135 passed in 20.95s1 failed, then1 passedwith the fix restored6 passeduv run python -m pytest -n auto -q:3324 passed, 2 skipped in 146.14suv run ruff format --check:148 files already formatteduv run ruff check:All checks passed!uv run ty check src/pepper_cli/:All checks passed!All checks passed on
3dd17ca(run 43550566728), including thecijob's coverage ratchet. Both automated reviews left no findings on the two changed files. Nothing was run against a live Pepper server.Deferred work. Items 1, 3, and 4 of #122 remain separate. This PR does not touch the clock-skew item. The replay-boundary documentation is no longer deferred because #250 landed it. The comment-boundary defect was found in a cross-consumer audit of the event stream and is fixed here rather than deferred.
Checklist (repo-specific)
uv run ruff format --checkanduv run ruff checkclean (no new lint ignores):148 files already formatted,All checks passed!uv run ty check src/pepper_cli/clean:All checks passed!uv run pytestpasses; coverage floor in.claude/maintainerd.jsonnot lowered: localuv run python -m pytest -n auto -qgave3324 passed, 2 skipped; the floor is unchanged; CI run 43550566728's Test and measure coverage and Coverage ratchet steps passed on3dd17ca/api/v1usage stays additive-only,# MIRROR:comments updated, unknown fields and frames still tolerated: none; no/api/v1call or# MIRROR:dataclass changed, and the client now tolerates unknown comments instead of treating them as a boundarySKILL.mdupdated if commands or output changed: not applicable; no command or output changedhelp/cli.mdupdated for a new or changed command, or not user-facing because the change only affects first-run replay state and adds regression tests