Skip to content

feat(annotate-last): --exclude-active-turn ignores the launching turn - #1375

Open
BenNewman100 wants to merge 2 commits into
backnotprop:mainfrom
BenNewman100:feat/annotate-last-exclude-active-turn
Open

feat(annotate-last): --exclude-active-turn ignores the launching turn#1375
BenNewman100 wants to merge 2 commits into
backnotprop:mainfrom
BenNewman100:feat/annotate-last-exclude-active-turn

Conversation

@BenNewman100

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in --exclude-active-turn flag to plannotator annotate-last / last. On the Claude Code path it restricts message selection (and the picker) to assistant messages written before the active turn — everything from the newest human prompt onward is ignored. Same semantics the Codex path already applies unconditionally via beforeActiveTurn.

Problem

annotate-last reads the transcript once at startup and takes the newest assistant text on the active branch, with no length or content filter. That is correct for the !-inline /plannotator-last slash command, where the binary runs before the agent gets a turn.

It breaks when the agent launches the command itself — e.g. a skill that runs plannotator annotate-last --json from its shell tool in the background (the only launch style that survives the Claude Code Bash tool's command timeout on long reviews). The agent is then free to write to the transcript after the process starts, and its acknowledgement ("Opened it for you.") races the read and usually wins. The user is shown the ack instead of the message they asked for; the intended message is still in the picker, but the default is wrong every time. Prompting the agent to "emit no text in the launching turn" is not reliable enough to fix this.

Why a flag, not default-on

The !-inline path can't use a turn cutoff: Claude Code records the slash command's user entry only after the bang command finishes, so at read time the newest human prompt on disk belongs to the previous turn and the cutoff would skip the message the user wants. Default-on would break existing /plannotator-last users; opt-in leaves that path byte-identical and lets model-invoked launchers pass the flag.

Also considered: ignoring entries newer than the process start time. It needs no flag and works for every launch style, but it only removes text written after launch — a preamble the agent writes before the tool call still wins. The turn cutoff covers both, so this PR goes with the flag.

Changes

  • session-log.ts: findActiveTurnStartIndex (newest human prompt, restricted to the active branch so a /rewind orphan can't anchor), an excludeActiveTurn option, and getRecentRenderedMessagesDetailed which reports emptiedByActiveTurn. getRecentRenderedMessages keeps its signature and delegates.
  • Fail-open: if the cutoff empties the active branch (right after a /compact the branch holds only the launching turn), the cutoff is redone in file order — mirroring the existing empty-branch fallback — so the pre-compaction messages are still offered.
  • index.ts: parse the flag with the other globals; pass it through in tryLogCandidates. A log that is emptied by the cutoff stops the candidate walk and exits 1 with No assistant message precedes the current turn (--exclude-active-turn). — previously an empty result meant "wrong file" and the walk could drift to an older session's transcript.
  • Flag is accepted on every path; stdin / Codex / Droid / Copilot ignore it.
  • cli.ts: usage + option text. Docs: commands/annotate-last.md gets a section on when to pass it.

Tests

session-log.test.ts — 9 new cases: ack after launch, preamble before launch, unchanged default without the option, multi-chunk message before the anchor, /rewind + cutoff, no human prompt, fresh /compact fail-open, nothing before the turn (emptiedByActiveTurn), anchor helper respects the branch set. cli.test.ts — help text.

Verified live from a Claude Code session launching the CLI from a Bash tool call: without the flag it selects the agent's in-turn text; with the flag it selects the message preceding the turn's prompt.

When an agent launches annotate-last from its own tool call (rather than
a !-inline slash command), it can still write to the transcript after the
process starts, and its acknowledgement becomes "the last message". The
Codex path already excludes the active turn unconditionally; Claude Code
did not.

- --exclude-active-turn (opt-in, Claude Code path): select and list only
  assistant messages before the newest human prompt on the active branch
- Fail open to file order when the cutoff empties the branch (fresh
  /compact), mirroring the existing empty-branch fallback
- A cutoff that empties the right log is reported and stops the candidate
  walk instead of drifting to an older session
- Flag accepted and inert on the stdin, Codex, Droid, and Copilot paths
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant