Skip to content

fix(hook): route annotate-last to the live Copilot CLI session - #1150

Merged
backnotprop merged 4 commits into
backnotprop:mainfrom
mararn1618:copilot-session-detection
Jul 30, 2026
Merged

fix(hook): route annotate-last to the live Copilot CLI session#1150
backnotprop merged 4 commits into
backnotprop:mainfrom
mararn1618:copilot-session-detection

Conversation

@mararn1618

@mararn1618 mararn1618 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Fixes Issue #1149,

Under Copilot CLI, plannotator last / annotate-last has no Copilot branch: the handler tries --stdin, Codex and droid, then falls through to the default transcript reader and silently annotates a message from an unrelated old session. The env check meant to catch Copilot (process.env.COPILOT_CLI) can never fire because Copilot CLI does not set that variable, and on a full install the shared plannotator-last skill shadows the Copilot plugin command, so this broken path is what /plannotator-last actually runs.

Approach

Copilot CLI leaves no env fingerprint, but every live session holds $COPILOT_HOME/session-state/<uuid>/inuse.<pid>.lock with the pid of its copilot process. The hook walks its ancestor pids (the existing getAncestorPids in session-log.ts) and matches them against those locks, which resolves the exact session that spawned the hook instead of guessing by cwd and mtime. Two guards:

  • locks can outlive their session and pids get reused, so a match only counts while the lock pid still names a copilot process (ps -o comm=)
  • where the process walk or ps is unavailable (Windows), detection resolves nothing and behavior stays exactly as today

annotate-last takes the Copilot branch when a lock matches, or when PLANNOTATOR_ORIGIN=copilot-cli is set (then with the existing cwd heuristic as fallback), reads messages through the existing reader in copilot-session.ts (getRecentCopilotMessages), and reports origin copilot-cli to the annotate server. The stdin, Codex and droid branches keep priority and are untouched. copilot-last prefers the lock match over its cwd heuristic. The global origin chain in index.ts is deliberately not modified.

Commits

  • fix(hook): add Copilot session lock detection. Matcher plus ancestor resolver in copilot-session.ts, unit tests, and the stale COPILOT_CLI=1 header claim corrected.
  • fix(hook): route annotate-last to the live Copilot session. The new branch and origin reporting.
  • fix(hook): prefer ancestor lock match in copilot-last. Separate commit, easy to drop if you would rather keep the pure cwd heuristic there.
  • docs(cli): document copilot-last in help. The subcommand worked but was missing from --help.

Testing

  • 13 new unit tests (copilot-session.test.ts): lock match, no match, several live sessions, pid order in the chain, malformed lock names, stale lock skipped, pid-reuse guard, failing process name lookup, COPILOT_HOME override.

  • Full bun test: no change against an untouched main checkout on my machine apart from the 13 new passing tests; the few suites that fail here fail identically before and after (environment related).

  • End to end inside a real Copilot session (macOS 26.5.2, Copilot CLI 1.0.75) with PLANNOTATOR_DEBUG=1:

    [DEBUG] Copilot detected, project root: /private/tmp/.../copilot-e2e
    [DEBUG] Copilot ancestor lock session: /Users/work1618/.copilot/session-state/0782e7a1-e654-485f-b822-4e1618703d1b
    [DEBUG] Copilot selected session: /Users/work1618/.copilot/session-state/0782e7a1-e654-485f-b822-4e1618703d1b
    

    The resolved uuid is the invoking session: the same run's summary footer printed copilot --resume=0782e7a1-e654-485f-b822-4e1618703d1b.

  • Negative control from a terminal outside Copilot: no Copilot lines in the debug output, the default transcript resolution runs unchanged.

  • PLANNOTATOR_ORIGIN=copilot-cli outside Copilot: the Copilot branch is taken with no lock match and the session resolves via the cwd heuristic.

Possible follow-up

The same lock detection could back the global origin chain in index.ts so every mode gains Copilot awareness. Left out to keep this change scoped to the -last flows.

Copilot CLI exports no identifying environment variable, so nothing distinguishes a Copilot session from a plain shell. Match ancestor pids against session-state inuse locks to find the live session, and only accept a match when the lock owner still names a copilot process, since locks can outlive their session and pids get reused.
Under Copilot CLI, annotate-last silently fell back to the default transcript reader and annotated a message from a different tool. Take the Copilot branch when an ancestor process holds a session lock, or when PLANNOTATOR_ORIGIN=copilot-cli is set with the cwd heuristic as fallback, and report origin copilot-cli to the annotate server.
The cwd heuristic can pick a stale session when several exist for one repo. Resolve the session locked by an ancestor copilot process first and keep the heuristic as fallback.
The subcommand worked but was missing from the top-level usage and the per-subcommand help map.
@backnotprop
backnotprop merged commit 5d1544f into backnotprop:main Jul 30, 2026
13 checks passed
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.

2 participants