Summary
When a worker is created with a custom branch from the New Task flow, the daemon creates the correct git worktree branch, but the session detail UI displays a synthetic session/<session-id> branch instead of the real branch.
This makes the session topbar and overview panel misleading: users cannot tell which branch the agent is actually working on, even though the backend has already persisted the correct branch in session metadata.
Severity
P2 user-visible correctness bug.
This does not appear to corrupt worktrees or block task creation, but it shows incorrect branch information in the main session view.
Reproduced On
- Repo: upstream
main
- Local checkout: clean before/after QA
- App mode: daemon + renderer against isolated local state
- Date reproduced: 2026-06-21
- Temp state only: isolated local temp directory, referred to below as
<qa-temp-dir>
- No live GitHub write flows or real user projects used for reproduction
Repro Steps
-
Start the daemon with isolated state, for example:
AO_DATA_DIR=<qa-temp-dir>/ao-data \
AO_RUN_FILE=<qa-temp-dir>/running.json \
AO_PORT=31001 \
AO_TELEMETRY_EVENTS=off \
AO_TELEMETRY_METRICS=off \
AO_TELEMETRY_REMOTE=off \
go run ./cmd/ao daemon
-
Start the renderer against that daemon.
-
Add a local temp git project.
-
Open the app's New Task flow.
-
Create a worker with:
- Title:
QA modal worker
- Branch:
qa/modal-worker
- Agent:
codex
- Brief: any harmless prompt
-
Open the created session detail page.
Expected Behavior
The session detail page should show the actual branch created for the worker:
This should be visible anywhere the session branch is rendered, including the session topbar and overview panel.
Actual Behavior
The session detail page shows:
The underlying worktree is actually on the requested branch:
$ git -C <qa-temp-dir>/ao-data/worktrees/demo-app/demo-app-3 symbolic-ref --short HEAD
qa/modal-worker
The daemon API response for the same session does not expose any branch field, so the frontend has no real branch to display:
{
"session": {
"id": "demo-app-3",
"projectId": "demo-app",
"issueId": "QA modal worker",
"kind": "worker",
"harness": "codex",
"activity": {
"state": "active",
"lastActivityAt": "2026-06-21T06:05:01.494546Z"
},
"isTerminated": false,
"createdAt": "2026-06-21T06:04:51.787185Z",
"updatedAt": "2026-06-21T06:05:01.494546Z",
"status": "working",
"terminalHandleId": "demo-app-3/terminal_0",
"prs": []
}
}
Screenshot evidence:
The UI shows session/demo-app-3 in the topbar and Overview > Branch, while the actual worktree branch is qa/modal-worker.
Screenshot attached in the first issue comment: https://github.com/user-attachments/assets/e2629441-fab8-42f1-8fa9-02194c9d86ec
Code Path / Likely Root Cause
The backend accepts and uses the requested branch during spawn:
backend/internal/httpd/controllers/dto.go
SpawnSessionRequest includes Branch string json:"branch,omitempty".
backend/internal/httpd/controllers/sessions.go
spawn passes Branch: in.Branch into ports.SpawnConfig.
backend/internal/session_manager/manager.go
Spawn uses cfg.Branch, falling back only when it is empty.
- It then creates the workspace with that branch.
- It persists
domain.SessionMetadata{Branch: ws.Branch, ...}.
backend/internal/domain/session.go
SessionMetadata contains Branch string.
However, the read model does not expose that persisted branch:
backend/internal/domain/session.go
SessionRecord.Metadata is json:"-".
backend/internal/httpd/controllers/dto.go
SessionView embeds domain.Session, so the metadata branch is omitted from the API response.
The frontend then fabricates a branch because the API does not provide one:
The New Task flow does send the user-entered branch correctly:
frontend/src/renderer/components/NewTaskDialog.tsx
- sends
branch: cleanBranch || undefined in the POST /api/v1/sessions body.
So the bug appears to be in the read/API/display path, not in the form submission path or workspace creation path.
Suggested Fix Shape
Expose the persisted branch on the session API read model, then consume it in the frontend.
Possible backend shape:
- Add a top-level
branch field to SessionView, sourced from domain.Session.Metadata.Branch.
- Keep the current metadata encapsulation if desired; the API only needs the display-safe branch string.
- Regenerate OpenAPI/frontend schema with
npm run api if the wire contract changes.
Possible frontend shape:
branch: session.branch ?? `session/${session.id}`,
The fallback can remain defensive for older/partial data, but current daemon-backed sessions should display the real persisted branch.
Suggested Tests
Backend:
- Add or extend a session controller/read-model test to verify that a session with
Metadata.Branch serializes a branch field.
- If there is already a spawn/get integration-style test, assert that a custom spawn branch round-trips through
GET /api/v1/sessions/{id} or GET /api/v1/sessions.
Frontend:
- Add/extend
useWorkspaceQuery coverage so an API session with branch: "qa/modal-worker" maps to WorkspaceSession.branch === "qa/modal-worker".
- Add a fallback test for missing branch, if the synthetic
session/<id> fallback is intentionally retained.
Verification commands for the fix:
cd backend && go test ./internal/httpd/... ./internal/session_manager/...
npm run api
npm --prefix frontend run typecheck
npm --prefix frontend test -- useWorkspaceQuery
Duplicate / Overlap Check
I searched current issues and PRs for:
branch session UI display
New task branch
session branch
- exact evidence/title terms like
session/demo-app and qa/modal-worker
No existing issue matched this exact bug.
Closest related work checked:
Contribution Value
This should be a good focused contribution because:
- It is user-visible and easy to verify locally.
- It does not require private credentials or live GitHub PR creation.
- The backend already stores the correct source of truth.
- The fix should stay inside existing daemon/API/frontend mapping boundaries.
- The PR can be small: expose one read-model field, regenerate schema, update the frontend mapper, and add regression coverage.
Summary
When a worker is created with a custom branch from the New Task flow, the daemon creates the correct git worktree branch, but the session detail UI displays a synthetic
session/<session-id>branch instead of the real branch.This makes the session topbar and overview panel misleading: users cannot tell which branch the agent is actually working on, even though the backend has already persisted the correct branch in session metadata.
Severity
P2 user-visible correctness bug.
This does not appear to corrupt worktrees or block task creation, but it shows incorrect branch information in the main session view.
Reproduced On
main<qa-temp-dir>Repro Steps
Start the daemon with isolated state, for example:
Start the renderer against that daemon.
Add a local temp git project.
Open the app's New Task flow.
Create a worker with:
QA modal workerqa/modal-workercodexOpen the created session detail page.
Expected Behavior
The session detail page should show the actual branch created for the worker:
This should be visible anywhere the session branch is rendered, including the session topbar and overview panel.
Actual Behavior
The session detail page shows:
The underlying worktree is actually on the requested branch:
The daemon API response for the same session does not expose any branch field, so the frontend has no real branch to display:
{ "session": { "id": "demo-app-3", "projectId": "demo-app", "issueId": "QA modal worker", "kind": "worker", "harness": "codex", "activity": { "state": "active", "lastActivityAt": "2026-06-21T06:05:01.494546Z" }, "isTerminated": false, "createdAt": "2026-06-21T06:04:51.787185Z", "updatedAt": "2026-06-21T06:05:01.494546Z", "status": "working", "terminalHandleId": "demo-app-3/terminal_0", "prs": [] } }Screenshot evidence:
Code Path / Likely Root Cause
The backend accepts and uses the requested branch during spawn:
backend/internal/httpd/controllers/dto.goSpawnSessionRequestincludesBranch string json:"branch,omitempty".backend/internal/httpd/controllers/sessions.gospawnpassesBranch: in.Branchintoports.SpawnConfig.backend/internal/session_manager/manager.goSpawnusescfg.Branch, falling back only when it is empty.domain.SessionMetadata{Branch: ws.Branch, ...}.backend/internal/domain/session.goSessionMetadatacontainsBranch string.However, the read model does not expose that persisted branch:
backend/internal/domain/session.goSessionRecord.Metadataisjson:"-".backend/internal/httpd/controllers/dto.goSessionViewembedsdomain.Session, so the metadata branch is omitted from the API response.The frontend then fabricates a branch because the API does not provide one:
frontend/src/renderer/hooks/useWorkspaceQuery.tscurrently maps every daemon-backed session with:
frontend/src/renderer/types/workspace.tsWorkspaceSession.branchis required, so the synthetic fallback is always displayed.The New Task flow does send the user-entered branch correctly:
frontend/src/renderer/components/NewTaskDialog.tsxbranch: cleanBranch || undefinedin thePOST /api/v1/sessionsbody.So the bug appears to be in the read/API/display path, not in the form submission path or workspace creation path.
Suggested Fix Shape
Expose the persisted branch on the session API read model, then consume it in the frontend.
Possible backend shape:
branchfield toSessionView, sourced fromdomain.Session.Metadata.Branch.npm run apiif the wire contract changes.Possible frontend shape:
The fallback can remain defensive for older/partial data, but current daemon-backed sessions should display the real persisted branch.
Suggested Tests
Backend:
Metadata.Branchserializes abranchfield.GET /api/v1/sessions/{id}orGET /api/v1/sessions.Frontend:
useWorkspaceQuerycoverage so an API session withbranch: "qa/modal-worker"maps toWorkspaceSession.branch === "qa/modal-worker".session/<id>fallback is intentionally retained.Verification commands for the fix:
Duplicate / Overlap Check
I searched current issues and PRs for:
branch session UI displayNew task branchsession branchsession/demo-appandqa/modal-workerNo existing issue matched this exact bug.
Closest related work checked:
frontend/src/renderer/hooks/useWorkspaceQuery.ts. Possible file-level rebase conflict, but no semantic overlap.Contribution Value
This should be a good focused contribution because: