Problem
Sessions created before the repo_id column existed have repo_id = NULL and only a project name. The client reconstructs a repo by fuzzy name match, so the sidebar and header show a repo. The server nulls every repo field for those sessions, so the coordinator has no repo tools (sandbox_start, spawn_worktree, open_pr are filtered out when ctx.repoId is null). The user sees a project; the agent says it has none.
Six modules still branch on the project name as a fallback.
Where
neon/migrations/20260812120000_control_sessions_repo.sql adds the column with no backfill.
app/api/control/chat/_lib/context.ts:111-121 returns repoId: null and overrides whatever the client sent.
lib/control/session-project.ts:25-45 (resolveControlSessionRepo) does the name match on the client.
- Name-based fallbacks:
lib/control/session-groups.ts:83, components/control/session-list.tsx:63,134,383, components/control/control-shell.tsx:371, app/api/control/sessions/route.ts:117.
Fix
- Write a Neon migration that sets
repo_id where the session's project name matches exactly one connected repo for that user or team (same rule as resolveControlSessionRepo). Leave ambiguous rows null and log their count in the migration comment.
- Commit the migration file in the repo. Never apply it through the MCP tool without the file (see
project_live_db_changes_need_migration_files in the project notes: applying without a file blocks production deploys).
- Add a
tests/db/ case that inserts a legacy row, runs migrations, and asserts repo_id is populated.
- For sessions that remain null, make the UI say so: group them under "Unlinked" with a tooltip "This session has no linked repository. Start a new mission to link one." instead of guessing.
Exit criteria
Out of scope
Removing every name-based fallback in one PR. Once the backfill lands, the fallbacks become dead code and can be removed one module at a time.
https://claude.ai/code/session_01SQ4nS96XYRztd5w9QkubPf
Problem
Sessions created before the
repo_idcolumn existed haverepo_id = NULLand only a project name. The client reconstructs a repo by fuzzy name match, so the sidebar and header show a repo. The server nulls every repo field for those sessions, so the coordinator has no repo tools (sandbox_start,spawn_worktree,open_prare filtered out whenctx.repoIdis null). The user sees a project; the agent says it has none.Six modules still branch on the project name as a fallback.
Where
neon/migrations/20260812120000_control_sessions_repo.sqladds the column with no backfill.app/api/control/chat/_lib/context.ts:111-121returnsrepoId: nulland overrides whatever the client sent.lib/control/session-project.ts:25-45(resolveControlSessionRepo) does the name match on the client.lib/control/session-groups.ts:83,components/control/session-list.tsx:63,134,383,components/control/control-shell.tsx:371,app/api/control/sessions/route.ts:117.Fix
repo_idwhere the session's project name matches exactly one connected repo for that user or team (same rule asresolveControlSessionRepo). Leave ambiguous rows null and log their count in the migration comment.project_live_db_changes_need_migration_filesin the project notes: applying without a file blocks production deploys).tests/db/case that inserts a legacy row, runs migrations, and assertsrepo_idis populated.Exit criteria
neon/migrations/and applies cleanly on a fresh branch (pnpm test:db).select count(*) from control_sessions where repo_id is nullagainst production and post the before and after numbers in the PR.pnpm lint,pnpm typecheck,pnpm test:dbpass.Out of scope
Removing every name-based fallback in one PR. Once the backfill lands, the fallbacks become dead code and can be removed one module at a time.
https://claude.ai/code/session_01SQ4nS96XYRztd5w9QkubPf