You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Panel says no GitHub repository after /trailhead:new created one mid-session #299
In a brand-new folder, /trailhead:new offers to create the GitHub repository and does create it (with its origin remote). Opening the panel afterwards, in the same session, still shows Not in a GitHub repository: open the session in a trailhead repo, although the repo now exists and is configured.
Repro
mkdir /tmp/fresh && cd /tmp/fresh, start a Claude Code session there (no git repo, no remote).
Run /trailhead:new <idea>; accept the offer to create the GitHub repository.
Open the panel (/trailhead:panel).
Expected: the panel pins the new repo and shows the map. Actual: the "Not in a GitHub repository" message.
Suspected area
plugins/trailhead/hooks/mod/repo-pin.ts (refreshPin / nextPin) and map-pane.tsx (syncPin, NO_REPO). Likely the repo read ($.session.repo(), or the pin computed at session start) is taken before the repo exists and is never refreshed once the remote appears, so the pin stays null for the rest of the session. To confirm: whether $.session.repo() is cached by the host, and whether opening the pane triggers a fresh refreshPin.
Acceptance
After a repo and origin remote are created mid-session, opening (or refreshing) the panel pins the new repo without restarting the session.
A regression test covers the "no repo at start, repo appears later" transition.
In a fresh directory with no git repo and no remote, start a Claude Code session with the trailhead mod loaded.
Run /trailhead:new <idea> and accept the offer to create the GitHub repository (gh repo create ... --source . --remote origin), so the directory becomes a git repo with an origin remote mid-session.
Open the panel (/trailhead:panel), or let a turn end.
Expected: the panel pins the new repo and shows the map.
Observed: the panel keeps showing Not in a GitHub repository: open the session in a trailhead repo for the rest of the session.
Code path read so far: the pin is recomputed by refreshPin (hooks/mod/repo-pin.ts) on session.start, on every turn.complete, and on every pane refresh (decide in map-pane.tsx), each time from $.session.repo(). So a stale pin points either at $.session.repo() (or the toplevel read) still answering "no repo" after the repo appears, or at the null-pin render path never asking for a refresh. The diagnosis will tell which.
answer: $.session.repo() does not pick up a repository created after the session started: the host keeps answering null for the rest of the session (contrary to its "read on each call" doc), and refreshPin (hooks/mod/repo-pin.ts) takes the pin only from $.session.repo(). When it answers null, nextPin keeps the current pin (null), and toplevelOf (git rev-parse --show-toplevel) is only consulted when repo() is non-null, so git is never asked. AND-gate: (a) the host's repo() stays null AND (b) the mod has no git fallback for root and origin remote when repo() answers null. (b) is the mod defect to fix. Falsifiable: in one session started outside any repo, after git init + git remote add origin mid-session, git rev-parse --show-toplevel in the cwd succeeds while $.session.repo() is still null; if repo() returned the new repo the hypothesis would be false.
evidence: Live headless run, Claude Code 2.1.306, plugin loaded with --plugin-dir, a fresh non-git temp dir, refreshPin temporarily instrumented with a [DEBUG-299] log of repo and toplevelOf; the session ran git init -q && git remote add origin https://github.com/octocat/hello-world.git. Log: both session.start calls repo=null top=null, the two calls before the git commands repo=null top=null, the two calls after them repo=null top=<the fresh dir>. Git sees the repo; the host's repo() stays null across later turn.complete calls. Probe removed afterwards. A regression test with session.repo answering null and git answering a repo with origin fails (nothing pinned), the rest of the suite (544) passes.
open questions: Whether the host's null is a permanent memo or stale for a window (null for at least two consecutive turn.complete calls, enough for the symptom). Whether to report the host behaviour upstream, as it contradicts its own doc. The run used git init + git remote add, not gh repo create --source=. --remote=origin --push (the new flow); both end with an origin remote in the cwd.
Eliminated: H2, the pane never re-syncs (it re-syncs on session.start, turn.complete and every refresh; each re-sync reads null). H3, the remote form is rejected (remoteId accepts https/ssh/scp forms, covered by tests/repo-pin.test.ts). H4, cwd mismatch (the new flow runs git init and gh repo create --source=. in the session cwd).
Ticket diff recomputed from the Refs: #299 trailer: 0a3003b, 9d09729, 287e5fa, read per commit. claude plugin test plugins/trailhead: 551 pass, 0 fail.
Findings ledger
W1 (warning): fixed (9d09729). whereIsSession (map-pane.tsx) read only $.session.repo(), so a work press in a repo created mid-session was refused as "cd into ". It now uses the shared currentRepo($), the same reader as refreshPin; covered by the pane test "a work press in the pinned repo goes through although session.repo answers null" on every surface.
I1 (info): fixed (9d09729, test 287e5fa). gitRepo returns null after a failed rev-parse (no origin read) and hands its toplevel on, so refreshPin and whereIsSession skip the repeated toplevelOf; the test "the git fallback spawns no more than it needs" checks both.
I2 (info): accepted, correct on reading. process.run rejecting: caught in gitRepo, last pin kept, press answers out. Null host answer in a linked worktree: origin read from the shared config, pin root is the worktree toplevel as on the host path. cwd moved to another repo: the fallback reads the new cwd, the pin moves as with the host answer.
I3 (info): accepted. A session outside any repo now costs one git rev-parse per pin refresh (none before); bounded by a 5 s timeout, and it is what makes the late repo visible.
A throw from $.session.repo() deliberately does not fall back to git: a throw is the host denying the checkout, not "no repo here", so the "checkout unreadable" refusal stays meaningful. The test-rig change in map-pane-ui.test.ts (gitRemote, origin answers exit 2 unless set) does not weaken existing tests.
No open findings.
Goal-backward verification
status: met
Ticket diff at the time of the check: 0a3003b. The later commits (9d09729, 287e5fa) only widen the same fallback to the work-press check and trim spawns; round-2 code review covers them.
Claim 1, after a repo and origin are created mid-session, opening or refreshing the panel pins the new repo without a restart: met. refreshPin falls back to git when the host's session.repo() answers null. Every pane sync goes through it (session.start, turn.complete, each decide, restoreAfterClear), and opening the panel with no turn in between does too: /trailhead:panel -> togglePanel -> openPane -> requestRefresh -> decide -> syncPin -> refreshPin. The refresh button and the empty-view fill take the same route; the band (register.tsx) uses the same refreshPin. A failed git read or a repo with no origin keeps the last pin.
Claim 2, a regression test covers the "no repo at start, repo appears later" transition: met. tests/repo-pin-late-repo.test.ts keeps session.repo null, makes git answer only after creation, fires turn.complete and asserts the pin; red before the fix, green after. The panel-open route is verified by reading, not by a test.
Tests
Stale-base preflight: current (main, 0 behind). npm test: 63 ok/skipped, 0 failed (includes claude plugin validate and claude plugin test, 551 pass). The regression test is red before the fix, green after.
Acceptance testing (AI-driven, live)
Claude Code 2.1.306 started in a tmux session in an empty temp directory (no git), plugin loaded from this checkout with --plugin-dir.
/trailhead:panel before any repo exists: pane shows Not in a GitHub repository: open the session in a trailhead repo. Expected, pass.
With the session still open, git init + git remote add origin https://github.com/ToRvaLDz/trailhead.git in that directory, then /trailhead:panel (close) and /trailhead:panel (open), no session restart: pane shows No active map, the whiteboard row and Maps · 5 with the repo's open maps read from GitHub. The repo is pinned. Pass.
Work press from the pane: not pressed live (it would really claim a ticket on the real repo); covered by the test "a work press in the pinned repo goes through although session.repo answers null" (red before 9d09729, green after).
Cause. Claude Code's $.session.repo() keeps answering null for a repository created after the session started (observed on 2.1.306, contrary to its "read on each call" doc), and the mod trusted it alone, so the repo pin stayed empty for the rest of the session.
Fix. A shared reader, currentRepo($) in hooks/mod/repo-pin.ts, takes the host's answer and, when it is null, reads git in the session cwd (git rev-parse --show-toplevel, then git remote get-url origin only when that succeeds). The pin refresh (pane and band) and the pane's work-press check both use it, so the panel pins a repo created mid-session and its work button no longer refuses it with "cd into". A failed git read or a repo with no origin keeps the last pin; a host read that throws still counts as an unreadable checkout.
Commits.
0a3003b fix(mod): pin a repo created mid-session by falling back to git (repo-pin.ts, tests/repo-pin-late-repo.test.ts, tests/repo-pin.test.ts)
9d09729 fix(mod): let a work press see a repo created mid-session (map-pane.tsx, repo-pin.ts, tests/map-pane-ui.test.ts)
287e5fa test(mod): cover the git fallback spawning no more than it needs (tests/repo-pin-late-repo.test.ts)
Verification. Review ledger, goal check and the live acceptance run are in the latest VERIFY comment: #299 (comment) (no open findings).
Activity
ToRvaLDz commentedon Oct 21, 2026
REPRO
Steps
/trailhead:new <idea>and accept the offer to create the GitHub repository (gh repo create ... --source . --remote origin), so the directory becomes a git repo with anoriginremote mid-session./trailhead:panel), or let a turn end.Expected: the panel pins the new repo and shows the map.
Observed: the panel keeps showing
Not in a GitHub repository: open the session in a trailhead repofor the rest of the session.Code path read so far: the pin is recomputed by
refreshPin(hooks/mod/repo-pin.ts) onsession.start, on everyturn.complete, and on every pane refresh (decideinmap-pane.tsx), each time from$.session.repo(). So a stale pin points either at$.session.repo()(or the toplevel read) still answering "no repo" after the repo appears, or at the null-pin render path never asking for a refresh. The diagnosis will tell which.ToRvaLDz commentedon Oct 21, 2026
DIAGNOSIS
status: found
answer:
$.session.repo()does not pick up a repository created after the session started: the host keeps answeringnullfor the rest of the session (contrary to its "read on each call" doc), andrefreshPin(hooks/mod/repo-pin.ts) takes the pin only from$.session.repo(). When it answers null,nextPinkeeps the current pin (null), andtoplevelOf(git rev-parse --show-toplevel) is only consulted whenrepo()is non-null, so git is never asked. AND-gate: (a) the host'srepo()stays null AND (b) the mod has no git fallback for root andoriginremote whenrepo()answers null. (b) is the mod defect to fix. Falsifiable: in one session started outside any repo, aftergit init+git remote add originmid-session,git rev-parse --show-toplevelin the cwd succeeds while$.session.repo()is still null; ifrepo()returned the new repo the hypothesis would be false.evidence: Live headless run, Claude Code 2.1.306, plugin loaded with
--plugin-dir, a fresh non-git temp dir,refreshPintemporarily instrumented with a[DEBUG-299]log ofrepoandtoplevelOf; the session rangit init -q && git remote add origin https://github.com/octocat/hello-world.git. Log: bothsession.startcallsrepo=null top=null, the two calls before the git commandsrepo=null top=null, the two calls after themrepo=null top=<the fresh dir>. Git sees the repo; the host'srepo()stays null across laterturn.completecalls. Probe removed afterwards. A regression test withsession.repoanswering null and git answering a repo withoriginfails (nothing pinned), the rest of the suite (544) passes.open questions: Whether the host's null is a permanent memo or stale for a window (null for at least two consecutive
turn.completecalls, enough for the symptom). Whether to report the host behaviour upstream, as it contradicts its own doc. The run usedgit init+git remote add, notgh repo create --source=. --remote=origin --push(thenewflow); both end with anoriginremote in the cwd.Eliminated: H2, the pane never re-syncs (it re-syncs on
session.start,turn.completeand every refresh; each re-sync reads null). H3, the remote form is rejected (remoteIdaccepts https/ssh/scp forms, covered bytests/repo-pin.test.ts). H4, cwd mismatch (thenewflow runsgit initandgh repo create --source=.in the session cwd).ToRvaLDz commentedon Oct 21, 2026
VERIFY
status: clean
critical: 0 · warning: 1 · info: 3
Ticket diff recomputed from the
Refs: #299trailer: 0a3003b, 9d09729, 287e5fa, read per commit.claude plugin test plugins/trailhead: 551 pass, 0 fail.Findings ledger
whereIsSession(map-pane.tsx) read only$.session.repo(), so a work press in a repo created mid-session was refused as "cd into ". It now uses the sharedcurrentRepo($), the same reader asrefreshPin; covered by the pane test "a work press in the pinned repo goes through although session.repo answers null" on every surface.gitReporeturns null after a failed rev-parse (no origin read) and hands its toplevel on, sorefreshPinandwhereIsSessionskip the repeatedtoplevelOf; the test "the git fallback spawns no more than it needs" checks both.process.runrejecting: caught ingitRepo, last pin kept, press answersout. Null host answer in a linked worktree: origin read from the shared config, pin root is the worktree toplevel as on the host path. cwd moved to another repo: the fallback reads the new cwd, the pin moves as with the host answer.git rev-parseper pin refresh (none before); bounded by a 5 s timeout, and it is what makes the late repo visible.A throw from
$.session.repo()deliberately does not fall back to git: a throw is the host denying the checkout, not "no repo here", so the "checkout unreadable" refusal stays meaningful. The test-rig change inmap-pane-ui.test.ts(gitRemote, origin answers exit 2 unless set) does not weaken existing tests.No open findings.
Goal-backward verification
status: met
Ticket diff at the time of the check: 0a3003b. The later commits (9d09729, 287e5fa) only widen the same fallback to the work-press check and trim spawns; round-2 code review covers them.
Claim 1, after a repo and
originare created mid-session, opening or refreshing the panel pins the new repo without a restart: met.refreshPinfalls back to git when the host'ssession.repo()answers null. Every pane sync goes through it (session.start,turn.complete, eachdecide,restoreAfterClear), and opening the panel with no turn in between does too:/trailhead:panel->togglePanel->openPane->requestRefresh->decide->syncPin->refreshPin. The refresh button and the empty-view fill take the same route; the band (register.tsx) uses the samerefreshPin. A failed git read or a repo with nooriginkeeps the last pin.Claim 2, a regression test covers the "no repo at start, repo appears later" transition: met.
tests/repo-pin-late-repo.test.tskeepssession.reponull, makes git answer only after creation, firesturn.completeand asserts the pin; red before the fix, green after. The panel-open route is verified by reading, not by a test.Tests
Stale-base preflight: current (main, 0 behind).
npm test: 63 ok/skipped, 0 failed (includesclaude plugin validateandclaude plugin test, 551 pass). The regression test is red before the fix, green after.Acceptance testing (AI-driven, live)
Claude Code 2.1.306 started in a tmux session in an empty temp directory (no git), plugin loaded from this checkout with
--plugin-dir./trailhead:panelbefore any repo exists: pane showsNot in a GitHub repository: open the session in a trailhead repo. Expected, pass.git init+git remote add origin https://github.com/ToRvaLDz/trailhead.gitin that directory, then/trailhead:panel(close) and/trailhead:panel(open), no session restart: pane showsNo active map, the whiteboard row andMaps · 5with the repo's open maps read from GitHub. The repo is pinned. Pass.ToRvaLDz commentedon Oct 21, 2026
Resolved.
Cause. Claude Code's
$.session.repo()keeps answeringnullfor a repository created after the session started (observed on 2.1.306, contrary to its "read on each call" doc), and the mod trusted it alone, so the repo pin stayed empty for the rest of the session.Fix. A shared reader,
currentRepo($)inhooks/mod/repo-pin.ts, takes the host's answer and, when it is null, reads git in the session cwd (git rev-parse --show-toplevel, thengit remote get-url originonly when that succeeds). The pin refresh (pane and band) and the pane's work-press check both use it, so the panel pins a repo created mid-session and its work button no longer refuses it with "cd into". A failed git read or a repo with nooriginkeeps the last pin; a host read that throws still counts as an unreadable checkout.Commits.
repo-pin.ts,tests/repo-pin-late-repo.test.ts,tests/repo-pin.test.ts)map-pane.tsx,repo-pin.ts,tests/map-pane-ui.test.ts)tests/repo-pin-late-repo.test.ts)Verification. Review ledger, goal check and the live acceptance run are in the latest VERIFY comment: #299 (comment) (no open findings).
fix(mod): pin a repo created mid-session by falling back to git
fix(mod): let a work press see a repo created mid-session
test(mod): cover the git fallback spawning no more than it needs