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
A PR opened from a session's worktree never auto-attaches to that session unless its head branch is exactly the spawn branch (ao/<name>). The SCM observer discovers a session's PR by querying gh pr list --head <session.branch>, but session.branch is frozen at the value set at spawn time and is never updated when the agent/user checks out a feature branch. Since the normal workflow is to do work on a feature branch (e.g. fix/...) and open a PR from it, the pr row is never created, and every downstream PR surface (sidebar, Session Inspector, /prs, kanban badge) shows "no PR".
The branch shouldn't have to equal the spawn branch — the system is designed to track the agent's actual branch (a git/gh wrapper exists for exactly this). That tracking mechanism is fully disconnected in the Go rewrite.
Source: surfaced while triaging why PR #281 (session reverbcode-3, branch ao/reverbcode-3, PR head fix/bug-triage-reverbcode-stack) showed no attached PR in the sidebar. | Analyzed against:6ac65d2 Confidence: High — traced the full data path and confirmed with live daemon logs + DB state.
Reproduction
Spawn a worker session (gets branch ao/<name>).
In its worktree, create a feature branch: git checkout -b fix/something.
Commit, push, and open a PR from fix/something (whether via raw gh pr create or any flow).
Expected: the PR attaches to the session (a pr row is written; sidebar/inspector show it).
Actual: nothing attaches. ~/.ao/data/ao.dbpr table stays empty; daemon.log logs scm observer: no PR detected for branch ... branch=ao/<name>.
Verified live: pr table empty across all sessions; daemon.log shows "scm observer: no PR detected for branch" session=reverbcode-1 branch=ao/reverbcode-orchestrator.
Root Cause
The observer keys PR discovery on the session's stored branch, read from SQLite, set at spawn and never refreshed:
The mechanism that is supposed to keep that branch in sync with the worktree's real branch is a git/gh wrapper (~/.ao/bin/git, ~/.ao/bin/ao-metadata-helper.sh) ported verbatim from the upstream TypeScript app. It calls update_ao_metadata branch "$3" on git checkout -b / git switch -c. In the Go rewrite this is triply disconnected:
Not on the session PATH. The session PATH pin (HookPATH in backend/internal/session_manager/manager.go) prepends only the daemon binary's own directory so bare ao resolves to the daemon. It does not add ~/.ao/bin, so the git/gh shims never run inside spawned sessions. Nothing in backend/ references ~/.ao/bin — those shims appear to be orphaned artifacts of an older (npm) AO install.
Writes to a store the daemon doesn't use. Even if invoked, update_ao_metadata writes to a per-session file $AO_DATA_DIR/<session>.json and first guards on that file already existing ([[ -f "$metadata_file" ]] || return 0). ReverbCode's source of truth is SQLite (~/.ao/data/ao.db) and never creates those per-session JSON files — confirmed: ~/.ao/data/reverbcode-3.json does not exist. So the call is a silent no-op.
No daemon path to update the branch. There is no hook event (backend/internal/cli/hooks.go has no branch handling) and no endpoint that writes sessions.branch from the agent's actual branch. The one place that could (backend/internal/service/session/claim_pr.go:126) is stubbed: // TODO: implement workspace branch checkout with BranchChanged: false hard-coded.
Net effect: sessions.branch is permanently ao/<name>, so DetectPRByBranch only ever matches a PR pushed on that exact branch.
Why upstream doesn't hit this
AgentWrapper/agent-orchestrator also matches by branch (gh pr list --head <session.branch> in packages/plugins/scm-github/src/index.ts:detectPR), but its git/gh wrapper (packages/core/src/agent-workspace-hooks.ts) writes to the file-based session metadata that is its source of truth, and the lifecycle manager reads it back. So when the agent runs git checkout -b fix/..., metadata.branch follows, and detection finds the PR. ReverbCode ported the wrapper but moved the source of truth to SQLite and never reconnected the sync path.
Fix (options, not prescriptive)
Track the real branch (matches upstream intent): add a workspace/git hook that reports branch changes to the daemon, and a daemon path that updates sessions.branch (then the existing DetectPRByBranch works). This is the branch-self-discovery assumption Wire PR engine into the daemon (built but unwired) #86 already bakes in — it needs the branch to actually be live.
Don't depend solely on branch name: also resolve a session's PR by the worktree's pushed head SHA / commits, or by gh pr list --head $(git -C <worktree> rev-parse --abbrev-ref HEAD) evaluated against the live worktree rather than the stored spawn branch.
Finish manual claim as the documented fallback: implement the claim_pr.go:126 TODO so ao session claim-pr <id> <pr> is a reliable escape hatch (it currently attaches the pr row but never adopts the branch).
Remove the orphaned ~/.ao/bin git/gh shims (or wire them) so the dead branch-sync path isn't mistaken for working.
Impact
PR auto-attachment silently fails for the default agent workflow (work on a feature branch). Every PR surface in the app stays empty even though the daemon's observer is running correctly.
Workaround exists but is manual/non-obvious: push on the spawn branch ao/<name>, or run ao session claim-pr <session-id> <pr-number>.
Related
#86 — Wire PR engine into the daemon; its "branch self-discovery" approach assumes SessionMetadata.Branch is live, which this bug shows it isn't.
#240 / #251 — frontend never renders PR facts. Those are downstream of this: they assume the pr row exists; this bug is why it often doesn't.
Bug
A PR opened from a session's worktree never auto-attaches to that session unless its head branch is exactly the spawn branch (
ao/<name>). The SCM observer discovers a session's PR by queryinggh pr list --head <session.branch>, butsession.branchis frozen at the value set at spawn time and is never updated when the agent/user checks out a feature branch. Since the normal workflow is to do work on a feature branch (e.g.fix/...) and open a PR from it, theprrow is never created, and every downstream PR surface (sidebar, Session Inspector,/prs, kanban badge) shows "no PR".The branch shouldn't have to equal the spawn branch — the system is designed to track the agent's actual branch (a
git/ghwrapper exists for exactly this). That tracking mechanism is fully disconnected in the Go rewrite.Source: surfaced while triaging why PR #281 (session
reverbcode-3, branchao/reverbcode-3, PR headfix/bug-triage-reverbcode-stack) showed no attached PR in the sidebar. | Analyzed against:6ac65d2Confidence: High — traced the full data path and confirmed with live daemon logs + DB state.
Reproduction
ao/<name>).git checkout -b fix/something.fix/something(whether via rawgh pr createor any flow).prrow is written; sidebar/inspector show it).~/.ao/data/ao.dbprtable stays empty;daemon.loglogsscm observer: no PR detected for branch ... branch=ao/<name>.Verified live:
prtable empty across all sessions;daemon.logshows"scm observer: no PR detected for branch" session=reverbcode-1 branch=ao/reverbcode-orchestrator.Root Cause
The observer keys PR discovery on the session's stored branch, read from SQLite, set at spawn and never refreshed:
backend/internal/observe/scm/observer.go:419—branch := strings.TrimSpace(sess.Metadata.Branch)backend/internal/observe/scm/observer.go:526—pr, err := o.provider.DetectPRByBranch(ctx, s.repo, s.branch)The mechanism that is supposed to keep that branch in sync with the worktree's real branch is a
git/ghwrapper (~/.ao/bin/git,~/.ao/bin/ao-metadata-helper.sh) ported verbatim from the upstream TypeScript app. It callsupdate_ao_metadata branch "$3"ongit checkout -b/git switch -c. In the Go rewrite this is triply disconnected:HookPATHinbackend/internal/session_manager/manager.go) prepends only the daemon binary's own directory so bareaoresolves to the daemon. It does not add~/.ao/bin, so thegit/ghshims never run inside spawned sessions. Nothing inbackend/references~/.ao/bin— those shims appear to be orphaned artifacts of an older (npm) AO install.update_ao_metadatawrites to a per-session file$AO_DATA_DIR/<session>.jsonand first guards on that file already existing ([[ -f "$metadata_file" ]] || return 0). ReverbCode's source of truth is SQLite (~/.ao/data/ao.db) and never creates those per-session JSON files — confirmed:~/.ao/data/reverbcode-3.jsondoes not exist. So the call is a silent no-op.backend/internal/cli/hooks.gohas no branch handling) and no endpoint that writessessions.branchfrom the agent's actual branch. The one place that could (backend/internal/service/session/claim_pr.go:126) is stubbed:// TODO: implement workspace branch checkoutwithBranchChanged: falsehard-coded.Net effect:
sessions.branchis permanentlyao/<name>, soDetectPRByBranchonly ever matches a PR pushed on that exact branch.Why upstream doesn't hit this
AgentWrapper/agent-orchestratoralso matches by branch (gh pr list --head <session.branch>inpackages/plugins/scm-github/src/index.ts:detectPR), but itsgit/ghwrapper (packages/core/src/agent-workspace-hooks.ts) writes to the file-based session metadata that is its source of truth, and the lifecycle manager reads it back. So when the agent runsgit checkout -b fix/...,metadata.branchfollows, and detection finds the PR. ReverbCode ported the wrapper but moved the source of truth to SQLite and never reconnected the sync path.Fix (options, not prescriptive)
sessions.branch(then the existingDetectPRByBranchworks). This is the branch-self-discovery assumption Wire PR engine into the daemon (built but unwired) #86 already bakes in — it needs the branch to actually be live.gh pr list --head $(git -C <worktree> rev-parse --abbrev-ref HEAD)evaluated against the live worktree rather than the stored spawn branch.claim_pr.go:126TODO soao session claim-pr <id> <pr>is a reliable escape hatch (it currently attaches theprrow but never adopts the branch).~/.ao/bingit/gh shims (or wire them) so the dead branch-sync path isn't mistaken for working.Impact
ao/<name>, or runao session claim-pr <session-id> <pr-number>.Related
SessionMetadata.Branchis live, which this bug shows it isn't.prrow exists; this bug is why it often doesn't.ao session get.