Skip to content

Panel says no GitHub repository after /trailhead:new created one mid-session #299

Closed

Description

@ToRvaLDz

Parent: 🗺 trailhead mod: Claude Code function hooks for on-screen state, context and in-process guards

Problem

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

  1. mkdir /tmp/fresh && cd /tmp/fresh, start a Claude Code session there (no git repo, no remote).
  2. Run /trailhead:new <idea>; accept the offer to create the GitHub repository.
  3. Open the panel (/trailhead:panel).
  4. 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.

Activity

  1. self-assigned this
    on Oct 21, 2026
  2. ToRvaLDz commented on Oct 21, 2026

    @ToRvaLDz
    OwnerAuthor

    REPRO

    Steps

    1. In a fresh directory with no git repo and no remote, start a Claude Code session with the trailhead mod loaded.
    2. 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.
    3. 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.

  3. ToRvaLDz commented on Oct 21, 2026

    @ToRvaLDz
    OwnerAuthor

    DIAGNOSIS

    status: found

    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).

  4. ToRvaLDz commented on Oct 21, 2026

    @ToRvaLDz
    OwnerAuthor

    VERIFY

    status: clean
    critical: 0 · warning: 1 · info: 3

    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.

    1. /trailhead:panel before any repo exists: pane shows Not in a GitHub repository: open the session in a trailhead repo. Expected, pass.
    2. 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.
    3. 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).
  5. ToRvaLDz commented on Oct 21, 2026

    @ToRvaLDz
    OwnerAuthor

    Resolved.

    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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions