Environment
- plannotator 0.24.2 via install.sh (the code paths referenced below are unchanged on current main, a54b46b)
- GitHub Copilot CLI 1.0.75
- macOS 26.5.2
Reproduce on a fresh machine
- Full install:
curl -fsSL https://plannotator.ai/install.sh | bash. Among other things this writes the shared agent skills to ~/.agents/skills/, including plannotator-last.
- In Copilot CLI:
/plugin marketplace add backnotprop/plannotator, then /plugin install plannotator-copilot@plannotator, restart.
- Open Copilot CLI in a repo that has older transcripts under
~/.claude/projects/ for the same cwd (the normal case on a machine where plannotator's other integrations were used before).
- Ask Copilot anything, then run
/plannotator-last.
What happens
The annotation UI opens with a message from an old transcript under ~/.claude/projects/<encoded-cwd>/, not the message Copilot just produced. No error, no warning; a message from an unrelated session of a different tool is presented as the thing to annotate. If no transcript exists for the cwd, the command fails to find a session instead. Either way the live Copilot session is never read.
Expected: /plannotator-last annotates the newest assistant message of the live Copilot session (~/.copilot/session-state/<uuid>/events.jsonl), or fails loudly.
Root cause chain
- The installer writes an agent-agnostic
plannotator-last skill to ~/.agents/skills/ (from apps/skills/core/plannotator-last/SKILL.md) whose body runs plannotator last. Copilot CLI auto-discovers ~/.agents/skills/, and that skill wins the name /plannotator-last over the plugin command shipped with plannotator-copilot, which would run the correct plannotator copilot-last (apps/copilot/commands/plannotator-last.md). The plugin path is never taken.
plannotator last / annotate-last (apps/hook/server/index.ts, handler at line 1113) branches --stdin, then CODEX_THREAD_ID, then droid, then falls through to the default transcript reader for ~/.claude/projects/. There is no Copilot branch.
- The origin detection that should catch this checks
process.env.COPILOT_CLI (index.ts:326), and the copilot-session.ts header states "The COPILOT_CLI=1 environment variable is set in Copilot CLI sessions". Copilot CLI sets no such variable, so the check can never fire and the fallthrough in step 2 always wins.
Evidence
Session events from the run where I hit this (~/.copilot/session-state/<uuid>/events.jsonl, fields trimmed, repo path shortened):
2026-07-27T14:14:01.586Z skill.invoked {"name": "plannotator-last", "path": "/Users/work1618/.agents/skills/plannotator-last/SKILL.md"}
2026-07-27T14:14:27.061Z tool.execution_start {"command": "cd <repo> && plannotator last"}
The dead env check, verified against the Copilot CLI 1.0.75 binary (compiled bundle, readable via strings):
$ strings "$(readlink -f "$(which copilot)")" | grep -o "COPILOT_CLI[A-Z_]*" | sort -u
COPILOT_CLI_BINARY_VERSION
COPILOT_CLI_DIST_DIR
It sets COPILOT_CLI_BINARY_VERSION for itself and reads COPILOT_CLI_DIST_DIR; nothing ever sets COPILOT_CLI.
What a live session does provide is a deterministic marker, session-state/<uuid>/inuse.<pid>.lock, where the pid is the live copilot process:
$ ls ~/.copilot/session-state/01e0a97a-8d76-4dac-bc23-291e59fda972/
checkpoints files inuse.40706.lock research workspace.yaml
$ ps -o pid=,comm= -p 40706
40706 copilot
Alternatives considered
An installer flag to skip ~/.agents/skills/ would not fix this: default installs stay broken, dropping the shared directory also removes the skills Codex reads from there (forcing a choice between Codex and Copilot on the same machine), and a hand-typed plannotator last under Copilot would still silently show the wrong content. Since the name collision cannot be removed, the fix that holds is teaching plannotator last to recognize the Copilot session it runs in.
I have this working end to end via the inuse locks; PR follows.
Environment
Reproduce on a fresh machine
curl -fsSL https://plannotator.ai/install.sh | bash. Among other things this writes the shared agent skills to~/.agents/skills/, includingplannotator-last./plugin marketplace add backnotprop/plannotator, then/plugin install plannotator-copilot@plannotator, restart.~/.claude/projects/for the same cwd (the normal case on a machine where plannotator's other integrations were used before)./plannotator-last.What happens
The annotation UI opens with a message from an old transcript under
~/.claude/projects/<encoded-cwd>/, not the message Copilot just produced. No error, no warning; a message from an unrelated session of a different tool is presented as the thing to annotate. If no transcript exists for the cwd, the command fails to find a session instead. Either way the live Copilot session is never read.Expected:
/plannotator-lastannotates the newest assistant message of the live Copilot session (~/.copilot/session-state/<uuid>/events.jsonl), or fails loudly.Root cause chain
plannotator-lastskill to~/.agents/skills/(fromapps/skills/core/plannotator-last/SKILL.md) whose body runsplannotator last. Copilot CLI auto-discovers~/.agents/skills/, and that skill wins the name/plannotator-lastover the plugin command shipped withplannotator-copilot, which would run the correctplannotator copilot-last(apps/copilot/commands/plannotator-last.md). The plugin path is never taken.plannotator last/annotate-last(apps/hook/server/index.ts, handler at line 1113) branches--stdin, thenCODEX_THREAD_ID, then droid, then falls through to the default transcript reader for~/.claude/projects/. There is no Copilot branch.process.env.COPILOT_CLI(index.ts:326), and thecopilot-session.tsheader states "The COPILOT_CLI=1 environment variable is set in Copilot CLI sessions". Copilot CLI sets no such variable, so the check can never fire and the fallthrough in step 2 always wins.Evidence
Session events from the run where I hit this (
~/.copilot/session-state/<uuid>/events.jsonl, fields trimmed, repo path shortened):The dead env check, verified against the Copilot CLI 1.0.75 binary (compiled bundle, readable via strings):
It sets
COPILOT_CLI_BINARY_VERSIONfor itself and readsCOPILOT_CLI_DIST_DIR; nothing ever setsCOPILOT_CLI.What a live session does provide is a deterministic marker,
session-state/<uuid>/inuse.<pid>.lock, where the pid is the live copilot process:Alternatives considered
An installer flag to skip
~/.agents/skills/would not fix this: default installs stay broken, dropping the shared directory also removes the skills Codex reads from there (forcing a choice between Codex and Copilot on the same machine), and a hand-typedplannotator lastunder Copilot would still silently show the wrong content. Since the name collision cannot be removed, the fix that holds is teachingplannotator lastto recognize the Copilot session it runs in.I have this working end to end via the inuse locks; PR follows.