From 2cfffa9028509365a47c9c88a7b5171c5c0f5d4f Mon Sep 17 00:00:00 2001 From: Archon Date: Fri, 17 Apr 2026 11:50:38 +0000 Subject: [PATCH 1/5] fix(core): pass configured default_branch to syncWorkspace to prevent hard-reset to wrong branch (#1273) syncWorkspace was always called with baseBranch=undefined, causing it to auto-detect origin/HEAD (typically main) and run git reset --hard on every message for managed clones, silently destroying feature-branch work. - Add default_branch to Codebase TypeScript interface - Update createCodebase to accept and persist default_branch - Detect actual post-clone branch in cloneRepository and store it - Pass toBranchName(codebase.default_branch) to syncWorkspace - Add default_branch to codebase API schema (OpenAPI) - Add PostgreSQL migration 022 and update 000_combined.sql baseline Co-Authored-By: Claude Sonnet 4.6 --- migrations/000_combined.sql | 1 + .../022_add_default_branch_to_codebases.sql | 6 ++++ packages/core/src/db/codebases.test.ts | 9 +++--- packages/core/src/db/codebases.ts | 11 ++++++-- packages/core/src/handlers/clone.ts | 28 +++++++++++++++++-- .../src/orchestrator/orchestrator-agent.ts | 7 +++-- .../src/orchestrator/orchestrator.test.ts | 1 + packages/core/src/types/index.ts | 1 + .../src/routes/schemas/codebase.schemas.ts | 1 + 9 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 migrations/022_add_default_branch_to_codebases.sql diff --git a/migrations/000_combined.sql b/migrations/000_combined.sql index 176963b40e..b189e077a0 100644 --- a/migrations/000_combined.sql +++ b/migrations/000_combined.sql @@ -29,6 +29,7 @@ CREATE TABLE IF NOT EXISTS remote_agent_codebases ( name VARCHAR(255) NOT NULL, repository_url VARCHAR(500), default_cwd VARCHAR(500) NOT NULL, + default_branch TEXT DEFAULT 'main', ai_assistant_type VARCHAR(20) DEFAULT 'claude', allow_env_keys BOOLEAN NOT NULL DEFAULT FALSE, commands JSONB DEFAULT '{}'::jsonb, diff --git a/migrations/022_add_default_branch_to_codebases.sql b/migrations/022_add_default_branch_to_codebases.sql new file mode 100644 index 0000000000..47f37e2ba9 --- /dev/null +++ b/migrations/022_add_default_branch_to_codebases.sql @@ -0,0 +1,6 @@ +-- Add default_branch column to remote_agent_codebases. +-- Mirrors the SQLite schema which already has this column (DEFAULT 'main'). +-- Existing rows get 'main' as the default; users on a different branch can +-- update the value directly or re-register the codebase. +ALTER TABLE remote_agent_codebases + ADD COLUMN IF NOT EXISTS default_branch TEXT DEFAULT 'main'; diff --git a/packages/core/src/db/codebases.test.ts b/packages/core/src/db/codebases.test.ts index 26c269a085..9427fabb27 100644 --- a/packages/core/src/db/codebases.test.ts +++ b/packages/core/src/db/codebases.test.ts @@ -35,6 +35,7 @@ describe('codebases', () => { name: 'test-project', repository_url: 'https://github.com/user/repo', default_cwd: '/workspace/test-project', + default_branch: 'main', ai_assistant_type: 'claude', commands: { plan: { path: '.claude/commands/plan.md', description: 'Plan feature' } }, created_at: new Date(), @@ -54,8 +55,8 @@ describe('codebases', () => { expect(result).toEqual(mockCodebase); expect(mockQuery).toHaveBeenCalledWith( - 'INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, ai_assistant_type) VALUES ($1, $2, $3, $4) RETURNING *', - ['test-project', 'https://github.com/user/repo', '/workspace/test-project', 'claude'] + 'INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, default_branch, ai_assistant_type) VALUES ($1, $2, $3, $4, $5) RETURNING *', + ['test-project', 'https://github.com/user/repo', '/workspace/test-project', null, 'claude'] ); }); @@ -73,8 +74,8 @@ describe('codebases', () => { expect(result).toEqual(codebaseWithoutOptional); expect(mockQuery).toHaveBeenCalledWith( - 'INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, ai_assistant_type) VALUES ($1, $2, $3, $4) RETURNING *', - ['test-project', null, '/workspace/test-project', 'claude'] + 'INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, default_branch, ai_assistant_type) VALUES ($1, $2, $3, $4, $5) RETURNING *', + ['test-project', null, '/workspace/test-project', null, 'claude'] ); }); diff --git a/packages/core/src/db/codebases.ts b/packages/core/src/db/codebases.ts index f3947fb6c1..5d3b1463e9 100644 --- a/packages/core/src/db/codebases.ts +++ b/packages/core/src/db/codebases.ts @@ -16,12 +16,19 @@ export async function createCodebase(data: { name: string; repository_url?: string; default_cwd: string; + default_branch?: string; ai_assistant_type?: string; }): Promise { const assistantType = data.ai_assistant_type ?? 'claude'; const result = await pool.query( - 'INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, ai_assistant_type) VALUES ($1, $2, $3, $4) RETURNING *', - [data.name, data.repository_url ?? null, data.default_cwd, assistantType] + 'INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, default_branch, ai_assistant_type) VALUES ($1, $2, $3, $4, $5) RETURNING *', + [ + data.name, + data.repository_url ?? null, + data.default_cwd, + data.default_branch ?? null, + assistantType, + ] ); if (!result.rows[0]) { throw new Error('Failed to create codebase: INSERT succeeded but no row returned'); diff --git a/packages/core/src/handlers/clone.ts b/packages/core/src/handlers/clone.ts index 366a951b8a..0da17f0bdd 100644 --- a/packages/core/src/handlers/clone.ts +++ b/packages/core/src/handlers/clone.ts @@ -40,7 +40,8 @@ export interface RegisterResult { async function registerRepoAtPath( targetPath: string, name: string, - repositoryUrl: string | null + repositoryUrl: string | null, + defaultBranch?: string ): Promise { // Auto-detect assistant type based on SDK folder conventions. // Built-in providers use well-known folders (.claude/, .codex/). @@ -125,6 +126,7 @@ async function registerRepoAtPath( name, repository_url: repositoryUrl ?? undefined, default_cwd: targetPath, + default_branch: defaultBranch ?? undefined, ai_assistant_type: suggestedAssistant, }); @@ -279,7 +281,29 @@ export async function cloneRepository(repoUrl: string): Promise await execFileAsync('git', ['config', '--global', '--add', 'safe.directory', targetPath]); getLog().debug({ path: targetPath }, 'safe_directory_added'); - const result = await registerRepoAtPath(targetPath, `${ownerName}/${repoName}`, workingUrl); + // Detect the actual branch checked out after clone (may differ from 'main' for repos with + // a non-default HEAD). Non-fatal: falls back to DB schema default if detection fails. + let detectedBranch: string | undefined; + try { + const { stdout } = await execFileAsync( + 'git', + ['-C', targetPath, 'rev-parse', '--abbrev-ref', 'HEAD'], + { timeout: 5000 } + ); + const branch = stdout.trim(); + if (branch && branch !== 'HEAD') { + detectedBranch = branch; + } + } catch { + // Non-fatal — leave undefined so DB default applies + } + + const result = await registerRepoAtPath( + targetPath, + `${ownerName}/${repoName}`, + workingUrl, + detectedBranch + ); getLog().info({ url: workingUrl, targetPath }, 'clone_completed'); return result; } diff --git a/packages/core/src/orchestrator/orchestrator-agent.ts b/packages/core/src/orchestrator/orchestrator-agent.ts index d5eb9397b3..797f28dbcd 100644 --- a/packages/core/src/orchestrator/orchestrator-agent.ts +++ b/packages/core/src/orchestrator/orchestrator-agent.ts @@ -27,7 +27,7 @@ import { toError } from '../utils/error'; import { getAgentProvider, getProviderCapabilities } from '@archon/providers'; import { getArchonHome, getArchonWorkspacesPath } from '@archon/paths'; import { syncArchonToWorktree } from '../utils/worktree-sync'; -import { syncWorkspace, toRepoPath } from '@archon/git'; +import { syncWorkspace, toRepoPath, toBranchName } from '@archon/git'; import type { WorkspaceSyncResult } from '@archon/git'; import { discoverWorkflowsWithConfig } from '@archon/workflows/workflow-discovery'; import { findWorkflow } from '@archon/workflows/router'; @@ -410,7 +410,10 @@ async function discoverAllWorkflows(conversation: Conversation): Promise; created_at: Date; diff --git a/packages/server/src/routes/schemas/codebase.schemas.ts b/packages/server/src/routes/schemas/codebase.schemas.ts index d2880a6be1..cbac16c6c7 100644 --- a/packages/server/src/routes/schemas/codebase.schemas.ts +++ b/packages/server/src/routes/schemas/codebase.schemas.ts @@ -15,6 +15,7 @@ export const codebaseSchema = z name: z.string(), repository_url: z.string().nullable(), default_cwd: z.string(), + default_branch: z.string().nullable(), ai_assistant_type: z.string(), commands: z.record(codebaseCommandSchema), created_at: z.string(), From ff75e20b2433007b9bcd00d94baa575c19974237 Mon Sep 17 00:00:00 2001 From: Archon Date: Fri, 17 Apr 2026 12:17:53 +0000 Subject: [PATCH 2/5] fix(core/test): address review findings for default_branch PR #1274 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed: - Add debug log to branch detection catch block in clone.ts (observability) - Update comments to accurately describe NULL → syncWorkspace auto-detect fallback - Add `default_branch` field to `makeCodebase` helper in clone.test.ts - Add `default_branch: null` to findCodebaseByDefaultCwd inline mock in codebases.test.ts - Add test for createCodebase with explicit non-null default_branch Tests added: - 4 branch detection tests in clone.test.ts (happy path, detached HEAD, failure, empty) - 2 syncWorkspace branch forwarding tests in orchestrator.test.ts (non-null + null branch) - Mock @archon/git in orchestrator.test.ts to verify syncWorkspace call arguments Co-Authored-By: Claude Sonnet 4.6 --- packages/core/src/db/codebases.test.ts | 18 +++++ packages/core/src/handlers/clone.test.ts | 66 +++++++++++++++++++ packages/core/src/handlers/clone.ts | 8 ++- .../src/orchestrator/orchestrator.test.ts | 66 +++++++++++++++++++ 4 files changed, 155 insertions(+), 3 deletions(-) diff --git a/packages/core/src/db/codebases.test.ts b/packages/core/src/db/codebases.test.ts index 9427fabb27..be062e8091 100644 --- a/packages/core/src/db/codebases.test.ts +++ b/packages/core/src/db/codebases.test.ts @@ -79,6 +79,23 @@ describe('codebases', () => { ); }); + test('creates codebase with explicit default_branch', async () => { + mockQuery.mockResolvedValueOnce( + createQueryResult([{ ...mockCodebase, default_branch: 'develop' }]) + ); + + await createCodebase({ + name: 'test-project', + default_cwd: '/workspace/test-project', + default_branch: 'develop', + }); + + expect(mockQuery).toHaveBeenCalledWith( + 'INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, default_branch, ai_assistant_type) VALUES ($1, $2, $3, $4, $5) RETURNING *', + ['test-project', null, '/workspace/test-project', 'develop', 'claude'] + ); + }); + test('defaults ai_assistant_type to claude', async () => { mockQuery.mockResolvedValueOnce(createQueryResult([mockCodebase])); @@ -297,6 +314,7 @@ describe('codebases', () => { id: 'cb-123', name: 'test-repo', default_cwd: '/workspace/test-repo', + default_branch: null, ai_assistant_type: 'claude', repository_url: null, commands: {}, diff --git a/packages/core/src/handlers/clone.test.ts b/packages/core/src/handlers/clone.test.ts index c913c1a78c..a62cebe012 100644 --- a/packages/core/src/handlers/clone.test.ts +++ b/packages/core/src/handlers/clone.test.ts @@ -131,6 +131,7 @@ function makeCodebase( name: string; repository_url: string | null; default_cwd: string; + default_branch: string | null; ai_assistant_type: string; }> = {} ): object { @@ -139,6 +140,7 @@ function makeCodebase( name: 'owner/repo', repository_url: 'https://github.com/owner/repo', default_cwd: '/home/test/.archon/workspaces/owner/repo/source', + default_branch: null, ai_assistant_type: 'claude', commands: {}, created_at: new Date(), @@ -301,6 +303,70 @@ describe('cloneRepository', () => { }); }); + // ── Branch detection after clone ────────────────────────────────────── + describe('branch detection after clone', () => { + test('stores detected branch in createCodebase call', async () => { + spyExecFileAsync.mockImplementation((_cmd: string, args: string[]) => { + if (args[0] === '-C' && args[2] === 'rev-parse') { + return Promise.resolve({ stdout: 'master\n', stderr: '' }); + } + return Promise.resolve({ stdout: '', stderr: '' }); + }); + mockCreateCodebase.mockResolvedValueOnce(makeCodebase() as ReturnType); + + await cloneRepository('https://github.com/owner/repo'); + + const createArg = mockCreateCodebase.mock.calls[0]?.[0] as { default_branch?: string }; + expect(createArg.default_branch).toBe('master'); + }); + + test('stores undefined (null in DB) when HEAD is detached', async () => { + spyExecFileAsync.mockImplementation((_cmd: string, args: string[]) => { + if (args[0] === '-C' && args[2] === 'rev-parse') { + return Promise.resolve({ stdout: 'HEAD\n', stderr: '' }); + } + return Promise.resolve({ stdout: '', stderr: '' }); + }); + mockCreateCodebase.mockResolvedValueOnce(makeCodebase() as ReturnType); + + await cloneRepository('https://github.com/owner/repo'); + + const createArg = mockCreateCodebase.mock.calls[0]?.[0] as { default_branch?: string }; + expect(createArg.default_branch).toBeUndefined(); + }); + + test('does not throw and stores undefined when rev-parse fails', async () => { + spyExecFileAsync.mockImplementation((_cmd: string, args: string[]) => { + if (args[0] === '-C' && args[2] === 'rev-parse') { + return Promise.reject(new Error('not a git repo')); + } + return Promise.resolve({ stdout: '', stderr: '' }); + }); + mockCreateCodebase.mockResolvedValueOnce(makeCodebase() as ReturnType); + + const result = await cloneRepository('https://github.com/owner/repo'); + expect(result).toBeDefined(); + + const createArg = mockCreateCodebase.mock.calls[0]?.[0] as { default_branch?: string }; + expect(createArg.default_branch).toBeUndefined(); + }); + + test('stores undefined when rev-parse returns empty string', async () => { + spyExecFileAsync.mockImplementation((_cmd: string, args: string[]) => { + if (args[0] === '-C' && args[2] === 'rev-parse') { + return Promise.resolve({ stdout: '', stderr: '' }); + } + return Promise.resolve({ stdout: '', stderr: '' }); + }); + mockCreateCodebase.mockResolvedValueOnce(makeCodebase() as ReturnType); + + await cloneRepository('https://github.com/owner/repo'); + + const createArg = mockCreateCodebase.mock.calls[0]?.[0] as { default_branch?: string }; + expect(createArg.default_branch).toBeUndefined(); + }); + }); + // ── Already-cloned directory ─────────────────────────────────────────── describe('pre-existing clone', () => { beforeEach(() => { diff --git a/packages/core/src/handlers/clone.ts b/packages/core/src/handlers/clone.ts index 0da17f0bdd..244813a12c 100644 --- a/packages/core/src/handlers/clone.ts +++ b/packages/core/src/handlers/clone.ts @@ -282,7 +282,8 @@ export async function cloneRepository(repoUrl: string): Promise getLog().debug({ path: targetPath }, 'safe_directory_added'); // Detect the actual branch checked out after clone (may differ from 'main' for repos with - // a non-default HEAD). Non-fatal: falls back to DB schema default if detection fails. + // a non-default HEAD). Non-fatal: if detection fails, NULL is stored and syncWorkspace + // auto-detects the branch at runtime (pre-existing behavior). let detectedBranch: string | undefined; try { const { stdout } = await execFileAsync( @@ -294,8 +295,9 @@ export async function cloneRepository(repoUrl: string): Promise if (branch && branch !== 'HEAD') { detectedBranch = branch; } - } catch { - // Non-fatal — leave undefined so DB default applies + } catch (err) { + // Non-fatal — store NULL so syncWorkspace falls back to auto-detection + getLog().debug({ path: targetPath, err }, 'clone.branch_detect_failed'); } const result = await registerRepoAtPath( diff --git a/packages/core/src/orchestrator/orchestrator.test.ts b/packages/core/src/orchestrator/orchestrator.test.ts index dd24c48bf4..9a9238172a 100644 --- a/packages/core/src/orchestrator/orchestrator.test.ts +++ b/packages/core/src/orchestrator/orchestrator.test.ts @@ -126,6 +126,17 @@ mock.module('../utils/worktree-sync', () => ({ syncArchonToWorktree: mockSyncArchonToWorktree, })); +// Git workspace sync mock +const mockSyncWorkspace = mock(() => Promise.resolve({ fetched: true, reset: false })); +const mockToRepoPath = mock((p: string) => p); +const mockToBranchName = mock((b: string) => b); + +mock.module('@archon/git', () => ({ + syncWorkspace: mockSyncWorkspace, + toRepoPath: mockToRepoPath, + toBranchName: mockToBranchName, +})); + // Orchestrator (isolation & dispatch) mocks const mockValidateAndResolveIsolation = mock(() => Promise.resolve({ status: 'existing', cwd: '/workspace/project', env: null }) @@ -279,6 +290,9 @@ function clearAllMocks(): void { mockExecuteWorkflow.mockClear(); mockFindWorkflow.mockClear(); mockSyncArchonToWorktree.mockClear(); + mockSyncWorkspace.mockClear(); + mockToRepoPath.mockClear(); + mockToBranchName.mockClear(); mockValidateAndResolveIsolation.mockClear(); mockDispatchBackgroundWorkflow.mockClear(); mockBuildOrchestratorPrompt.mockClear(); @@ -651,6 +665,58 @@ describe('orchestrator-agent handleMessage', () => { }); }); + // ─── syncWorkspace branch forwarding ────────────────────────────────── + + describe('syncWorkspace branch forwarding', () => { + test('passes configured default_branch to syncWorkspace when set', async () => { + const codbaseWithBranch: Codebase = { + ...mockCodebase, + default_branch: 'develop', + default_cwd: '/home/test/.archon/workspaces/owner/repo/source', + }; + mockGetOrCreateConversation.mockResolvedValue({ + ...mockConversationWithProject, + codebase_id: 'codebase-789', + }); + mockGetCodebase.mockResolvedValue(codbaseWithBranch); + mockClient.sendQuery.mockImplementation(async function* () { + yield { type: 'result', sessionId: 'session-id' }; + }); + + await handleMessage(platform, 'chat-456', 'help'); + + expect(mockSyncWorkspace).toHaveBeenCalledWith( + expect.any(String), + 'develop', + expect.any(Object) + ); + }); + + test('passes undefined branch to syncWorkspace when default_branch is null', async () => { + const codebaseNoBranch: Codebase = { + ...mockCodebase, + default_branch: null, + default_cwd: '/home/test/.archon/workspaces/owner/repo/source', + }; + mockGetOrCreateConversation.mockResolvedValue({ + ...mockConversationWithProject, + codebase_id: 'codebase-789', + }); + mockGetCodebase.mockResolvedValue(codebaseNoBranch); + mockClient.sendQuery.mockImplementation(async function* () { + yield { type: 'result', sessionId: 'session-id' }; + }); + + await handleMessage(platform, 'chat-456', 'help'); + + expect(mockSyncWorkspace).toHaveBeenCalledWith( + expect.any(String), + undefined, + expect.any(Object) + ); + }); + }); + // ─── Session Management ──────────────────────────────────────────────── describe('session management', () => { From 934f946ed277e2589bb2dd1fe1727dad3e29b158 Mon Sep 17 00:00:00 2001 From: Archon Date: Fri, 17 Apr 2026 12:22:00 +0000 Subject: [PATCH 3/5] simplify: remove redundant ?? undefined on already-optional defaultBranch Co-Authored-By: Claude Sonnet 4.6 --- packages/core/src/handlers/clone.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/handlers/clone.ts b/packages/core/src/handlers/clone.ts index 244813a12c..da8c2eafca 100644 --- a/packages/core/src/handlers/clone.ts +++ b/packages/core/src/handlers/clone.ts @@ -126,7 +126,7 @@ async function registerRepoAtPath( name, repository_url: repositoryUrl ?? undefined, default_cwd: targetPath, - default_branch: defaultBranch ?? undefined, + default_branch: defaultBranch, ai_assistant_type: suggestedAssistant, }); From abb64819efb701a63893ca491ab1eefe95888db2 Mon Sep 17 00:00:00 2001 From: Archon Date: Fri, 17 Apr 2026 16:04:59 +0000 Subject: [PATCH 4/5] fix(review): safe migration default + complete test fixtures for Codebase type - Migration 022: remove DEFAULT 'main' so existing rows stay NULL instead of being silently set to 'main', which would trigger the same hard-reset-to-main bug for managed clones on non-main branches during upgrade. - orchestrator-agent.test.ts: add default_branch: null to makeCodebase() and makeCodebaseForSync() to satisfy the updated Codebase interface (type-check would fail without this). - orchestrator-isolation.test.ts: add default_branch, repository_url, and ai_assistant_type to makeCodebase() for the same reason. Co-Authored-By: Claude Sonnet 4.6 --- migrations/022_add_default_branch_to_codebases.sql | 10 ++++++---- .../core/src/orchestrator/orchestrator-agent.test.ts | 2 ++ .../src/orchestrator/orchestrator-isolation.test.ts | 3 +++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/migrations/022_add_default_branch_to_codebases.sql b/migrations/022_add_default_branch_to_codebases.sql index 47f37e2ba9..5d1281898a 100644 --- a/migrations/022_add_default_branch_to_codebases.sql +++ b/migrations/022_add_default_branch_to_codebases.sql @@ -1,6 +1,8 @@ -- Add default_branch column to remote_agent_codebases. --- Mirrors the SQLite schema which already has this column (DEFAULT 'main'). --- Existing rows get 'main' as the default; users on a different branch can --- update the value directly or re-register the codebase. +-- NULL means "not yet detected"; syncWorkspace falls back to auto-detection +-- (pre-existing behaviour). New clones set this via the branch-detect path in +-- clone.ts. Using no DEFAULT so existing rows stay NULL rather than being +-- silently set to 'main' (which could trigger an unwanted hard-reset for +-- managed clones on a non-main branch). ALTER TABLE remote_agent_codebases - ADD COLUMN IF NOT EXISTS default_branch TEXT DEFAULT 'main'; + ADD COLUMN IF NOT EXISTS default_branch TEXT; diff --git a/packages/core/src/orchestrator/orchestrator-agent.test.ts b/packages/core/src/orchestrator/orchestrator-agent.test.ts index ab8165ca7e..f167fbe095 100644 --- a/packages/core/src/orchestrator/orchestrator-agent.test.ts +++ b/packages/core/src/orchestrator/orchestrator-agent.test.ts @@ -207,6 +207,7 @@ function makeCodebase(name: string, id = `id-${name}`): Codebase { name, repository_url: null, default_cwd: `/repos/${name}`, + default_branch: null, ai_assistant_type: 'claude', commands: {}, created_at: new Date(), @@ -830,6 +831,7 @@ function makeCodebaseForSync() { name: 'test-repo', repository_url: 'https://github.com/test/repo', default_cwd: '/repos/test-repo', + default_branch: null, ai_assistant_type: 'claude', commands: {}, created_at: new Date(), diff --git a/packages/core/src/orchestrator/orchestrator-isolation.test.ts b/packages/core/src/orchestrator/orchestrator-isolation.test.ts index 6bcbedb697..4eb0fb820a 100644 --- a/packages/core/src/orchestrator/orchestrator-isolation.test.ts +++ b/packages/core/src/orchestrator/orchestrator-isolation.test.ts @@ -175,7 +175,10 @@ function makeCodebase(overrides?: Partial): Codebase { return { id: 'cb-1', name: 'test-repo', + repository_url: null, default_cwd: '/workspace/test-repo', + default_branch: null, + ai_assistant_type: 'claude', commands: {}, created_at: new Date(), updated_at: new Date(), From b1f93a61683d661810416fb231db184d7b47d8ae Mon Sep 17 00:00:00 2001 From: Archon Date: Fri, 17 Apr 2026 16:20:40 +0000 Subject: [PATCH 5/5] fix(review): add SQLite migrateColumns entry for default_branch + fix WorkspaceSyncResult mock shape Fixes two code review issues from PR #1274: - H-1: Add ALTER TABLE migration for default_branch column in remote_agent_codebases so existing SQLite users get the column without data loss (NULL means syncWorkspace auto-detects the branch) - M-1: Fix WorkspaceSyncResult mock in orchestrator.test.ts to return the correct shape matching the interface (branch/synced/previousHead/newHead/updated) and import BranchName for proper type casting Co-Authored-By: Claude Sonnet 4.6 --- packages/core/src/db/adapters/sqlite.ts | 14 ++++++++++++++ .../core/src/orchestrator/orchestrator.test.ts | 11 ++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/core/src/db/adapters/sqlite.ts b/packages/core/src/db/adapters/sqlite.ts index 485706d040..09e7b17b9a 100644 --- a/packages/core/src/db/adapters/sqlite.ts +++ b/packages/core/src/db/adapters/sqlite.ts @@ -215,6 +215,20 @@ export class SqliteAdapter implements IDatabase { } catch (e: unknown) { getLog().warn({ err: e as Error }, 'db.sqlite_migration_session_columns_failed'); } + + // Codebases columns + try { + const codebaseCols = this.db.prepare("PRAGMA table_info('remote_agent_codebases')").all() as { + name: string; + }[]; + const codebaseColNames = new Set(codebaseCols.map(c => c.name)); + + if (!codebaseColNames.has('default_branch')) { + this.db.run('ALTER TABLE remote_agent_codebases ADD COLUMN default_branch TEXT'); + } + } catch (e: unknown) { + getLog().warn({ err: e as Error }, 'db.sqlite_migration_codebases_columns_failed'); + } } /** diff --git a/packages/core/src/orchestrator/orchestrator.test.ts b/packages/core/src/orchestrator/orchestrator.test.ts index 9a9238172a..d01f571681 100644 --- a/packages/core/src/orchestrator/orchestrator.test.ts +++ b/packages/core/src/orchestrator/orchestrator.test.ts @@ -5,6 +5,7 @@ import { makeTestWorkflow, makeTestWorkflowList } from '@archon/workflows/test-u import type { Conversation, Codebase, Session } from '../types'; import { ConversationNotFoundError } from '../types'; import type { WorkflowDefinition } from '@archon/workflows/schemas/workflow'; +import type { BranchName } from '@archon/git'; // ─── Mock setup (BEFORE importing module under test) ───────────────────────── @@ -127,7 +128,15 @@ mock.module('../utils/worktree-sync', () => ({ })); // Git workspace sync mock -const mockSyncWorkspace = mock(() => Promise.resolve({ fetched: true, reset: false })); +const mockSyncWorkspace = mock(() => + Promise.resolve({ + branch: 'main' as BranchName, + synced: true, + previousHead: 'abc12345', + newHead: 'abc12345', + updated: false, + }) +); const mockToRepoPath = mock((p: string) => p); const mockToBranchName = mock((b: string) => b);