Repository navigation
fix(claude-channel): use opening cursor for first-run boundary - #268
Draft
bniladridas wants to merge 1 commit into
Draft
bniladridas wants to merge 1 commit into
bniladridas wants to merge 1 commit into
Conversation
On a start=head connection, the opening cursor already names the feed head, so events above it are now classified as live and skip the local created_at cutoff, while --start beginning, a server that sends no opening cursor, and replay from a saved position keep the existing cutoff. The boundary is reset for every connection so a reconnect does not treat old missed messages as live. Four tests cover a skewed timestamp after the head, a zero head, genuine replay, and the reconnect case. Full suite 3364 passed, 2 skipped; coverage 95.70 against the 88 floor; ruff format, ruff check and ty clean. Refs Vycari#255. Refs Vycari#267. Signed-off-by: Niladri Das <125604915+bniladridas@users.noreply.github.com>
This was referenced Nov 15, 2026
This branch has not been deployed
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.
Human overview
On a
start=headconnection, the opening cursor now defines the first-run boundary: events above it are live and skip the localcreated_atcutoff. Replay from a saved position still uses the cutoff, and the boundary is reset on every reconnect so old missed messages are not pushed as live. Refs #255. Server-side atomicity of the head remains tracked by #267, and this stays draft until maintainers confirm it.Human required (optional)
start=headcursor is the atomic feed head before this leaves draft (server contract: Define a clock-free feed-head contract for first-run history #267).AI reviewer
Details for agents: what the diff does, invariants, verification, checklist
What the diff does. In
src/pepper_cli/claude_channel.py,ChannelWorkerstores the opening cursor of astart=headconnection inlive_from._handle_framerecords it only whilehead_pendingis true, before the first event._handle_eventnow receives the SSE frame id and treats an event withevent_id > live_fromas live, bypassing thecreated_at < cutofftest for both message classification and the unknown-kind resync path._followresetslive_fromat the start of every connection.In
tests/test_claude_channel.py, four tests cover an hour-behind message after head 30, a message after head 0, genuine replay under--start beginning, and a reconnect where the second connection has no opening cursor and replays an old event as skipped history.Invariants touched. The boundary is the opening head of this connection, not the saved resume position. A reconnect may replay messages missed while offline; reusing the first connection's head would mark those as live, so the reset is required.
--start beginningand servers that ignorestart=headnever setlive_from, so their existing cutoff behavior is unchanged. A cursor frame is still not a boundary in any case other than the first frame of a pending head start; a cursor that arrives before an event on a resume remains a skip marker.Alternatives rejected. The HTTP
Datecorrection was rolled back in #261 because the header can come from an intermediary and need not share the clock that stampscreated_at. Using the saved resume cursor as the boundary was rejected because events after it can be old messages missed while the channel was down. Waiting for #267 before any client change was rejected because the existing protocol already exposes the opening cursor and the remaining exposure is documented.Failure posture. If the opening cursor is not the true head, or the server can drop events between capturing it and subscribing, events at or below the real head could be classified as live. That does not corrupt state by itself, but it can push old history or suppress a genuine live message under clock skew. The per-connection reset limits the blast radius to one connection. #267 tracks the server guarantee.
Migration notes. No schema, ordering, or backfill change. Rollback is reverting this commit; clients then return to the local cutoff for all first-run events, which is the behavior documented in #261.
Where to look hardest. The
head_pendingguard in_handle_frame: only the first cursor of a connection may setlive_from. Moving that assignment outside the guard would turn a resume skip marker into a live boundary. The zero-head case is covered because the comparison isevent_id > live_from, not truthiness. The unknown-kind path bypasses the cutoff as well, so a post-head unknown event triggers resync even when its timestamp is behind.Verification.
uv run pytest tests/test_claude_channel.py -q: 136 passed. The four focused boundary tests: 4 passed.uv run pytest -n auto --cov --cov-report=json:coverage-summary.json: 3364 passed, 2 skipped, total coverage 95.70 against the 88 floor.uv run ruff format --check,uv run ruff check, anduv run ty check src/pepper_cli/: clean.git diff --check: clean. No manual CLI or UI run: the change is event classification inside the worker, and the tests drive that worker with a fake SSE transport. The 2 skips belong to the existing suite; this change adds none and I did not investigate them.Deferred work. Server-side atomicity, ordering, and the cross-endpoint head sequence are in #267. Older servers that ignore
start=head, and--start beginning, still rely on the local cutoff; the documented exposure remains in #255. The rollback and design note remain in #261.Checklist (repo-specific)
uv run ruff format --checkanduv run ruff checkclean (no new lint ignores). Ran both; passed.uv run ty check src/pepper_cli/clean. Ran; passed.uv run pytestpasses; coverage floor in.claude/maintainerd.jsonnot lowered. 3364 passed, 2 skipped; coverage 95.70 against 88./api/v1usage stays additive-only,# MIRROR:comments updated, unknown fields and frames still tolerated. No server-contract or mirrored-model change; the existingstart=headcursor is used as is.SKILL.mdupdated if commands or output changed. No command, flag, or output shape changed.help/cli.mdupdated for a new or changed command, or not user-facing because ___. Not user-facing: internal first-run event classification only.