Skip to content

fix(core): pass configured default_branch to syncWorkspace to prevent hard-reset to wrong branch#1274

Open
leex279 wants to merge 5 commits intodevfrom
fix/issue-1273-sync-workspace-respects-default-branch
Open

fix(core): pass configured default_branch to syncWorkspace to prevent hard-reset to wrong branch#1274
leex279 wants to merge 5 commits intodevfrom
fix/issue-1273-sync-workspace-respects-default-branch

Conversation

@leex279
Copy link
Copy Markdown
Collaborator

@leex279 leex279 commented Apr 17, 2026

Summary

Fixes #1273syncWorkspace was always called with baseBranch=undefined, causing it to auto-detect origin/HEAD (typically main) and run git reset --hard origin/main on every message for managed clones. This silently destroyed any feature-branch work in the source clone with no warning.

  • Add default_branch: string | null to the Codebase TypeScript interface
  • Update createCodebase in packages/core/src/db/codebases.ts to accept and persist default_branch
  • Detect the actual post-clone checked-out branch in cloneRepository (using git rev-parse --abbrev-ref HEAD) and pass it through registerRepoAtPath to the DB
  • Fix the syncWorkspace call in orchestrator-agent.ts to pass toBranchName(codebase.default_branch) instead of undefined
  • Add default_branch to the codebase API schema (codebase.schemas.ts) and update the OpenAPI baseline
  • Add PostgreSQL incremental migration 022_add_default_branch_to_codebases.sql and update 000_combined.sql baseline

Test plan

  • bun run type-check passes across all packages
  • bun run lint passes with zero warnings
  • bun test packages/core/src/db/codebases.test.ts — all 27 tests pass (updated assertions for new INSERT query)
  • bun test packages/core/src/orchestrator/orchestrator.test.ts — all 68 tests pass (updated mockCodebase to include default_branch)
  • Manual: register a managed clone, check out a feature branch, send a chat message → source clone remains on the feature branch
  • Manual: register a repo with a non-main default branch → default_branch in DB matches actual clone HEAD

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Codebases now detect and persist a repository's default branch after clone; sync uses a codebase's configured default branch when present.
    • API responses include each codebase's default_branch.
  • Database

    • Migration/schema updates add nullable storage for codebase default-branch (existing records remain unchanged unless updated).
  • Tests

    • Added and updated tests covering branch detection, persistence, sync behavior, and API/schema exposure.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a nullable default_branch field to codebase storage, records detected branch after clone, exposes the field in types and API schemas, and passes the configured branch into workspace sync so managed clones reset to the configured branch instead of always using the remote default.

Changes

Cohort / File(s) Summary
Database Schema
migrations/000_combined.sql, migrations/022_add_default_branch_to_codebases.sql
Add default_branch column to remote_agent_codebases (combined SQL sets schema default 'main'; migration adds default_branch via ALTER TABLE ... ADD COLUMN IF NOT EXISTS without backfilling existing rows).
Types & API Schema
packages/core/src/types/index.ts, packages/server/src/routes/schemas/codebase.schemas.ts
Add `default_branch: string
DB Operations & Tests
packages/core/src/db/codebases.ts, packages/core/src/db/codebases.test.ts
createCodebase accepts optional default_branch and inserts it (using data.default_branch ?? null); tests updated to include new bind param and assert null or explicit branch values.
Clone Handler & Tests
packages/core/src/handlers/clone.ts, packages/core/src/handlers/clone.test.ts
After clone, run git rev-parse --abbrev-ref HEAD (with timeout) to detect checked-out branch; forward detected non-empty/non-HEAD value as defaultBranch when registering the repo; errors are logged and do not throw. Tests cover branch, HEAD/empty, and error cases.
Orchestrator & Tests
packages/core/src/orchestrator/orchestrator-agent.ts, packages/core/src/orchestrator/orchestrator.test.ts, packages/core/src/orchestrator/orchestrator-agent.test.ts, packages/core/src/orchestrator/orchestrator-isolation.test.ts
When syncing a codebase, derive configuredBranch from codebase.default_branch via toBranchName and pass it into syncWorkspace (preserving resetAfterFetch behavior). Tests and fixtures updated to include default_branch and assert branch forwarding.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CloneHandler as Clone Handler
    participant Git
    participant DB as Database
    participant Orchestrator
    participant Sync as Workspace Sync

    User->>CloneHandler: request clone/register
    CloneHandler->>Git: git clone <repo>
    Git-->>CloneHandler: clone complete
    CloneHandler->>Git: git rev-parse --abbrev-ref HEAD (timeout)
    Git-->>CloneHandler: branch name / HEAD / error
    CloneHandler->>DB: createCodebase(..., default_branch=detected?)
    DB-->>CloneHandler: stored

    User->>Orchestrator: trigger workflow (message)
    Orchestrator->>DB: fetch codebase (includes default_branch)
    DB-->>Orchestrator: codebase with default_branch
    Orchestrator->>Sync: syncWorkspace(path, baseBranch=configuredBranch, {resetAfterFetch})
    Sync->>Git: git fetch origin
    Sync->>Git: git reset --hard origin/<configuredBranch>
    Git-->>Sync: reset complete
    Sync-->>Orchestrator: sync complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped and peered to see which head was true,

Sniffed the branch and whispered it to you,
I tucked that name into the DB neat,
Now sync won't stomp the work beneath our feet,
Carrots and commits both safe anew.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fix: passing configured default_branch to syncWorkspace to prevent incorrect hard-resets.
Description check ✅ Passed The description covers the problem, why it matters, what changed, and includes test validation and implementation details matching the template expectations.
Linked Issues check ✅ Passed The PR addresses all requirements from issue #1273: adds default_branch to Codebase interface, updates createCodebase to persist it, detects post-clone branch, and forwards it to syncWorkspace instead of undefined.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing issue #1273: database schema updates, branch detection in clone handlers, and syncWorkspace call corrections are all necessary to achieve the fix.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-1273-sync-workspace-respects-default-branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/handlers/clone.ts (1)

314-380: ⚠️ Potential issue | 🟡 Minor

registerRepository skips default-branch detection — managed-clone fix won't apply to locally registered repos.

cloneRepository now detects the actual checked-out branch and persists it, but registerRepository still calls registerRepoAtPath(localPath, name, remoteUrl) without a defaultBranch (Line 380). Local repos already have a valid working tree at localPath, so the same git -C <localPath> rev-parse --abbrev-ref HEAD detection is straightforward here. Without it, any locally registered repo whose actual branch isn't main falls back to the schema default 'main', which is precisely the failure mode #1273 aims to prevent once syncWorkspace starts trusting codebase.default_branch.

Proposed fix
   // default_cwd is the real local path (not the symlink)
-  return registerRepoAtPath(localPath, name, remoteUrl);
+  let detectedBranch: string | undefined;
+  try {
+    const { stdout } = await execFileAsync(
+      'git',
+      ['-C', localPath, '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
+  }
+  return registerRepoAtPath(localPath, name, remoteUrl, detectedBranch);

You could also hoist the detection into a small helper to avoid duplicating the block from cloneRepository.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/handlers/clone.ts` around lines 314 - 380,
registerRepository currently doesn't detect the repo's checked-out branch so
local repos get the schema default 'main'; add the same branch-detection used in
cloneRepository: run execFileAsync('git', ['-C', localPath, 'rev-parse',
'--abbrev-ref', 'HEAD']) (handle errors) to get defaultBranch and pass that
defaultBranch into the existing registerRepoAtPath(localPath, name, remoteUrl,
defaultBranch) call; optionally factor the execFileAsync branch lookup into a
small helper shared with cloneRepository to avoid duplication and reference the
functions registerRepository, registerRepoAtPath, and cloneRepository when
making the change.
🧹 Nitpick comments (4)
migrations/000_combined.sql (1)

32-32: Consider mirroring the idempotent ALTER at the bottom.

Other per-migration columns (e.g., allow_env_keys from migration 021 at Line 313-315) are both in the CREATE TABLE and re-stated as ALTER TABLE ... ADD COLUMN IF NOT EXISTS at the file's tail to handle upgrades from earlier combined-baseline snapshots. For consistency, add:

-- From migration 022: default_branch on codebases
ALTER TABLE remote_agent_codebases
  ADD COLUMN IF NOT EXISTS default_branch TEXT DEFAULT 'main';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/000_combined.sql` at line 32, Add an idempotent ALTER that mirrors
the CREATE TABLE column for default_branch: in the migrations file append an
"ALTER TABLE remote_agent_codebases ADD COLUMN IF NOT EXISTS default_branch TEXT
DEFAULT 'main';" (referencing the default_branch column and
remote_agent_codebases table introduced by migration 022) so upgrades from
earlier combined-baseline snapshots will succeed without errors.
packages/core/src/db/codebases.ts (1)

24-31: Explicit null bind overrides the column DEFAULT 'main' — intentional? Document it.

Passing data.default_branch ?? null always binds a value, so Postgres never applies the DEFAULT 'main' from migrations 000/022. In practice this is desirable for this PR (so /register-project callers that don't pass a branch get NULL rather than a misleading 'main', aligning with the string | null type), but it means the column default is effectively dead code for new inserts.

Two small asks:

  • Add a brief comment here explaining the NULL-means-"unknown, auto-detect" contract so a future contributor doesn't "fix" this back to using the DB default.
  • Consider dropping DEFAULT 'main' from the migration in favor of NULL (see the migration review) so the DB and application agree on the semantics.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/db/codebases.ts` around lines 24 - 31, The INSERT binds
data.default_branch with the expression `data.default_branch ?? null`, which
intentionally sends NULL to Postgres (so the app treats NULL as
"unknown/auto-detect" rather than letting the DB default 'main' apply); add a
short clarifying comment next to this binding in
packages/core/src/db/codebases.ts (referencing the SQL INSERT for
remote_agent_codebases and the `data.default_branch ?? null` expression) that
documents the NULL-means-"unknown/auto-detect" contract so future contributors
don't revert it to using the DB default, and also note in the comment to
consider removing `DEFAULT 'main'` from the migration so DB and app semantics
match.
packages/core/src/db/codebases.test.ts (1)

58-59: Missing test for the non-null default_branch insert path.

Both createCodebase tests currently exercise only the default_branch → null branch. Given this PR's core purpose is to persist the detected branch from cloneRepository, please add a test that passes default_branch: 'main' (or a non-main value) and asserts it appears in the bind params. This also guards against accidental ?? null regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/db/codebases.test.ts` around lines 58 - 59, Add a new unit
test in packages/core/src/db/codebases.test.ts that covers the non-null
default_branch insertion path: call the same helper that invokes createCodebase
(or the test helper that triggers the SQL shown: the INSERT INTO
remote_agent_codebases...) with default_branch: 'main' (or another non-null
value) and assert the executed query's bind params include 'main' in the fourth
position (matching the array ['test-project', 'https://github.com/user/repo',
'/workspace/test-project', 'main', 'claude']); this mirrors the existing
null-branch test but passes a non-null default_branch to guard against
accidental ?? null regressions.
packages/core/src/handlers/clone.ts (1)

284-299: Consider a debug log on detection failure.

Silent catch is documented, which satisfies the guideline, but swallowing without any signal makes future triage harder if default_branch ends up unexpectedly at the DB default. A single getLog().debug on the catch path would preserve the non-fatal behavior while giving operators a breadcrumb. Minor nit — feel free to ignore.

Proposed tweak
-  } catch {
+  } catch (error) {
     // Non-fatal — leave undefined so DB default applies
+    getLog().debug({ path: targetPath, err: error }, 'default_branch_detect_failed');
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/handlers/clone.ts` around lines 284 - 299, The catch block
swallowing errors when detecting the checked-out branch should emit a non-fatal
debug log so operators get a breadcrumb; modify the try/catch around
execFileAsync (the block that sets detectedBranch) to catch the error as a
variable (e.g., err) and call getLog().debug with a short message like "failed
to detect repo branch, falling back to DB default" and include the error details
and targetPath/command context; preserve the non-fatal behavior by leaving
detectedBranch undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@migrations/022_add_default_branch_to_codebases.sql`:
- Around line 5-6: The migration currently adds default_branch with DEFAULT
'main' which will materialize 'main' for all existing rows and can cause
destructive hard resets; change the ALTER TABLE in remote_agent_codebases (the
ADD COLUMN IF NOT EXISTS default_branch TEXT DEFAULT 'main') to add the column
without a non-NULL default (e.g., no DEFAULT or DEFAULT NULL) so existing rows
remain NULL, and instead implement a safe backfill step outside the migration
that reads each repository's current branch from disk and updates default_branch
only where appropriate; ensure application logic handles NULL by falling back to
local git branch detection (e.g., use the existing codepath that calls git
rev-parse --abbrev-ref HEAD) or skips resets.

In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 413-418: When codebase.default_branch is null we must avoid
falling back to origin/HEAD for managed clones; change the logic around
configuredBranch (the variable computed from codebase.default_branch before
calling syncWorkspace) so that if isManagedClone is true and
codebase.default_branch is null you run a local branch detection (git rev-parse
--abbrev-ref HEAD via the existing git helper or a small helper) and set
configuredBranch to that local branch; if local branch disagrees with
origin/HEAD, disable or refuse the hard reset (e.g., flip resetAfterFetch to
false or return an error) before calling syncWorkspace(toRepoPath(...),
configuredBranch, { resetAfterFetch: isManagedClone }); alternatively also
populate default_branch during handleRegisterProject so new rows never persist
null.

In `@packages/core/src/types/index.ts`:
- Line 61: Update the test helper functions named makeCodebase used in
orchestrator-agent.test.ts and orchestrator-isolation.test.ts to return complete
Codebase objects by adding the missing required fields: include default_branch:
string | null (e.g., "main" or null), repository_url: string | null (or an
actual repo URL), and ai_assistant_type: string | null (or appropriate
enum/string value) alongside the existing properties so the returned object
satisfies the Codebase type signature and fixes TypeScript compilation errors.

---

Outside diff comments:
In `@packages/core/src/handlers/clone.ts`:
- Around line 314-380: registerRepository currently doesn't detect the repo's
checked-out branch so local repos get the schema default 'main'; add the same
branch-detection used in cloneRepository: run execFileAsync('git', ['-C',
localPath, 'rev-parse', '--abbrev-ref', 'HEAD']) (handle errors) to get
defaultBranch and pass that defaultBranch into the existing
registerRepoAtPath(localPath, name, remoteUrl, defaultBranch) call; optionally
factor the execFileAsync branch lookup into a small helper shared with
cloneRepository to avoid duplication and reference the functions
registerRepository, registerRepoAtPath, and cloneRepository when making the
change.

---

Nitpick comments:
In `@migrations/000_combined.sql`:
- Line 32: Add an idempotent ALTER that mirrors the CREATE TABLE column for
default_branch: in the migrations file append an "ALTER TABLE
remote_agent_codebases ADD COLUMN IF NOT EXISTS default_branch TEXT DEFAULT
'main';" (referencing the default_branch column and remote_agent_codebases table
introduced by migration 022) so upgrades from earlier combined-baseline
snapshots will succeed without errors.

In `@packages/core/src/db/codebases.test.ts`:
- Around line 58-59: Add a new unit test in
packages/core/src/db/codebases.test.ts that covers the non-null default_branch
insertion path: call the same helper that invokes createCodebase (or the test
helper that triggers the SQL shown: the INSERT INTO remote_agent_codebases...)
with default_branch: 'main' (or another non-null value) and assert the executed
query's bind params include 'main' in the fourth position (matching the array
['test-project', 'https://github.com/user/repo', '/workspace/test-project',
'main', 'claude']); this mirrors the existing null-branch test but passes a
non-null default_branch to guard against accidental ?? null regressions.

In `@packages/core/src/db/codebases.ts`:
- Around line 24-31: The INSERT binds data.default_branch with the expression
`data.default_branch ?? null`, which intentionally sends NULL to Postgres (so
the app treats NULL as "unknown/auto-detect" rather than letting the DB default
'main' apply); add a short clarifying comment next to this binding in
packages/core/src/db/codebases.ts (referencing the SQL INSERT for
remote_agent_codebases and the `data.default_branch ?? null` expression) that
documents the NULL-means-"unknown/auto-detect" contract so future contributors
don't revert it to using the DB default, and also note in the comment to
consider removing `DEFAULT 'main'` from the migration so DB and app semantics
match.

In `@packages/core/src/handlers/clone.ts`:
- Around line 284-299: The catch block swallowing errors when detecting the
checked-out branch should emit a non-fatal debug log so operators get a
breadcrumb; modify the try/catch around execFileAsync (the block that sets
detectedBranch) to catch the error as a variable (e.g., err) and call
getLog().debug with a short message like "failed to detect repo branch, falling
back to DB default" and include the error details and targetPath/command
context; preserve the non-fatal behavior by leaving detectedBranch undefined.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4a5ffa44-0020-478c-bd43-a72da3a80c9a

📥 Commits

Reviewing files that changed from the base of the PR and between 922edbb and bc5a201.

📒 Files selected for processing (9)
  • migrations/000_combined.sql
  • migrations/022_add_default_branch_to_codebases.sql
  • packages/core/src/db/codebases.test.ts
  • packages/core/src/db/codebases.ts
  • packages/core/src/handlers/clone.ts
  • packages/core/src/orchestrator/orchestrator-agent.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
  • packages/core/src/types/index.ts
  • packages/server/src/routes/schemas/codebase.schemas.ts

Comment thread migrations/022_add_default_branch_to_codebases.sql Outdated
Comment on lines +413 to 418
const configuredBranch = codebase.default_branch
? toBranchName(codebase.default_branch)
: undefined;
syncResult = await syncWorkspace(toRepoPath(codebase.default_cwd), configuredBranch, {
resetAfterFetch: isManagedClone,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Null default_branch silently falls back to auto-detect — same root bug for unset rows.

When codebase.default_branch is null, configuredBranch is undefined and syncWorkspace re-enters the auto-detect path (git symbolic-ref refs/remotes/origin/HEAD → typically main). For managed clones (resetAfterFetch: true) this will still do git reset --hard origin/main — the exact behavior issue #1273 is trying to avoid.

This works for codebases created via the updated clone path (branch is detected and persisted), but two scenarios still hit the bug:

  • Rows that existed before migration 022 (defaulted to 'main' — see my comment on the migration).
  • Codebases created via /register-project (handleRegisterProject at Line 1300 doesn't pass default_branch, so null is persisted).

Consider either (a) detecting HEAD at /register-project time too, or (b) when default_branch is null on a managed clone, detecting the local branch via git rev-parse --abbrev-ref HEAD before syncing rather than resolving via origin/HEAD, and refusing the reset if they disagree.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 413 - 418,
When codebase.default_branch is null we must avoid falling back to origin/HEAD
for managed clones; change the logic around configuredBranch (the variable
computed from codebase.default_branch before calling syncWorkspace) so that if
isManagedClone is true and codebase.default_branch is null you run a local
branch detection (git rev-parse --abbrev-ref HEAD via the existing git helper or
a small helper) and set configuredBranch to that local branch; if local branch
disagrees with origin/HEAD, disable or refuse the hard reset (e.g., flip
resetAfterFetch to false or return an error) before calling
syncWorkspace(toRepoPath(...), configuredBranch, { resetAfterFetch:
isManagedClone }); alternatively also populate default_branch during
handleRegisterProject so new rows never persist null.

Comment thread packages/core/src/types/index.ts
@leex279
Copy link
Copy Markdown
Collaborator Author

leex279 commented Apr 17, 2026

🔍 Comprehensive PR Review

PR: #1274 — fix(core): pass configured default_branch to syncWorkspace
Reviewed by: 3 specialized agents (code-review, error-handling, test-coverage)
Date: 2026-04-17


Summary

The core fix is correct and well-scoped: default_branch is properly detected at clone time, stored via a safe IF NOT EXISTS migration, typed correctly throughout, and forwarded to syncWorkspace with a correct null-guard. All CI passes. The two blockers are test gaps — the new branch detection logic and the actual syncWorkspace argument forwarding (the whole point of the PR) have no behavioral assertions.

Verdict: REQUEST_CHANGES

Severity Count
🔴 CRITICAL 0
🟠 HIGH 2
🟡 MEDIUM 1
🟢 LOW 4

🟠 High Issues (Should Fix Before Merge)

1. cloneRepository branch detection logic is untested

📍 packages/core/src/handlers/clone.ts:286-306 / clone.test.ts

The new git rev-parse --abbrev-ref HEAD call that detects and stores the actual default branch has no test coverage. The default spyExecFileAsync mock returns { stdout: '', stderr: '' }, so all existing tests silently exercise only the null-fallback path. Three sub-cases are untested: happy path (branch detected), detached HEAD (stdout = 'HEAD\n' → should not be stored), and failure (git throws → should swallow non-fatally).

If the branch !== 'HEAD' filter were accidentally inverted, repos in detached HEAD state would store 'HEAD' as default_branch, causing syncWorkspace to hard-reset to a non-existent branch on every message.

View recommended tests
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());
    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 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());
    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());
    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();
  });
});

2. configuredBranch forwarding to syncWorkspace is untested

📍 packages/core/src/orchestrator/orchestrator-agent.ts:413-416 / orchestrator.test.ts

The core fix — deriving configuredBranch from codebase.default_branch and passing it to syncWorkspace — has no behavioral test. The only change in orchestrator.test.ts adds default_branch: 'main' to satisfy TypeScript; no assertion verifies what syncWorkspace receives. @archon/git is not mocked, so the test never observes the call arguments.

A refactor that accidentally removes configuredBranch or its null-guard would silently reintroduce #1273 with no test catching it.

View recommended tests
// At top of orchestrator.test.ts mock setup:
const mockSyncWorkspace = mock(() => Promise.resolve({ fetched: true, reset: false }));
mock.module('@archon/git', () => ({
  syncWorkspace: mockSyncWorkspace,
  toRepoPath: mock((p: string) => p),
  toBranchName: mock((b: string) => b),
}));

// New describe block:
describe('syncWorkspace branch forwarding', () => {
  test('passes configured default_branch to syncWorkspace when set', async () => {
    mockGetCodebase.mockResolvedValue({ ...mockCodebase, default_branch: 'develop' });
    // trigger handleMessage with codebase-scoped conversation
    expect(mockSyncWorkspace).toHaveBeenCalledWith(
      expect.any(String), 'develop', expect.any(Object)
    );
  });

  test('passes undefined branch to syncWorkspace when default_branch is null', async () => {
    mockGetCodebase.mockResolvedValue({ ...mockCodebase, default_branch: null });
    // trigger handleMessage
    expect(mockSyncWorkspace).toHaveBeenCalledWith(
      expect.any(String), undefined, expect.any(Object)
    );
  });
});

🟡 Medium Issue (Consider Fixing)

3. Silent catch block has no debug log

📍 packages/core/src/handlers/clone.ts:297-299

The catch block for branch detection failure emits no log — not even at debug level. Every other optional git call in the same file logs when it fails unexpectedly. When this catch fires in production (timeout, permission denied, corrupted .git/HEAD), there is no trace in logs, making it undiagnosable why default_branch ended up NULL.

Options: Fix now (one-liner) | Skip (comment satisfies CLAUDE.md documentation requirement)

View fix
} catch (err) {
  // Non-fatal — store NULL so syncWorkspace falls back to auto-detection
  getLog().debug({ path: targetPath, err }, 'clone.branch_detect_failed');
}

Also update the block comment at line 285 from "falls back to DB schema default" to "store NULL so syncWorkspace auto-detects at runtime" — the actual fallback is NULL→auto-detect, not the DB-level DEFAULT 'main'.


🟢 Low Issues

View 4 low-priority suggestions
Issue Location Suggestion
Comments misdescribe fallback as "DB default applies" — actual fallback is NULL → syncWorkspace auto-detect clone.ts:285,298 Update to: "store NULL so syncWorkspace falls back to auto-detection"
makeCodebase mock helper missing default_branch field clone.test.ts:128-148 Add default_branch: null to returned object and Partial<> type
findCodebaseByDefaultCwd inline mock missing default_branch codebases.test.ts:295-308 Add default_branch: null to inline mock row
createCodebase test never passes non-null default_branch codebases.test.ts:46-61 Add a test with default_branch: 'develop' asserting it reaches SQL params

✅ What's Good

  • Correct null-guard: codebase.default_branch ? toBranchName(...) : undefined avoids calling toBranchName('') which would throw
  • Safe migration: ADD COLUMN IF NOT EXISTS handles environments where the column was manually added
  • Timeout on detection: { timeout: 5000 } prevents branch detection from stalling the clone flow
  • 'HEAD' filter: Correctly handles detached HEAD — 'HEAD' is not stored as a branch name
  • execFileAsync compliance: New git call follows CLAUDE.md's requirement (not exec)
  • sanitizeError preserved: Credential sanitization in clone failure path is intact
  • TypeScript strict: No any introduced; default_branch: string | null typed correctly throughout
  • Well-scoped: No speculative changes — YAGNI followed

Next Steps

  1. ⚡ Add tests for the 2 HIGH issues (branch detection logic + syncWorkspace forwarding)
  2. 📝 Consider the MEDIUM debug log (one-liner)
  3. 🎯 Merge when tests are green

Reviewed by Archon comprehensive-pr-review workflow
Artifacts: /.archon/workspaces/coleam00/Archon/artifacts/runs/eb39ea1b-10b9-4475-9772-5644ad14839b/review/

leex279 pushed a commit that referenced this pull request Apr 17, 2026
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 <noreply@anthropic.com>
@leex279
Copy link
Copy Markdown
Collaborator Author

leex279 commented Apr 17, 2026

⚡ Self-Fix Report (Aggressive)

Status: COMPLETE
Pushed: ✅ Changes pushed to fix/issue-1273-sync-workspace-respects-default-branch
Commit: c0fcdd6
Philosophy: Fix everything unless clearly a new concern


Fixes Applied (7 total)

Severity Count
🔴 CRITICAL 0
🟠 HIGH 2
🟡 MEDIUM 1
🟢 LOW 4
View all fixes
  • Branch detection untested (clone.test.ts) — Added describe('branch detection after clone') with 4 tests: happy path, detached HEAD, rev-parse failure, empty stdout
  • configuredBranch forwarding untested (orchestrator.test.ts) — Mocked @archon/git; added 2 tests verifying syncWorkspace receives correct branch arg for non-null and null default_branch
  • Silent catch has no debug log (clone.ts:297) — Added getLog().debug({ path: targetPath, err }, 'clone.branch_detect_failed')
  • Comments misdescribe fallback (clone.ts:284,298) — Updated to accurately say "store NULL so syncWorkspace falls back to auto-detection"
  • makeCodebase mock missing default_branch (clone.test.ts:128) — Added default_branch: string | null to type and default_branch: null to returned object
  • findCodebaseByDefaultCwd inline mock missing default_branch (codebases.test.ts:295) — Added default_branch: null
  • createCodebase test never passes non-null branch (codebases.test.ts) — Added test verifying 'develop' flows through to SQL params

Tests Added

  • clone.test.ts: 4 tests in describe('branch detection after clone')
  • orchestrator.test.ts: 2 tests in describe('syncWorkspace branch forwarding') + @archon/git mock
  • codebases.test.ts: 1 test for createCodebase with explicit default_branch: 'develop'

Skipped (0)

(none — all findings addressed)


Suggested Follow-up Issues

(none)


Validation

✅ Type check | ✅ Lint | ✅ Tests (48 clone + 70 orchestrator + 28 codebases = 146 passed)

Note: 11 pre-existing @archon/paths test failures exist on the base branch and are unrelated to this PR.


Self-fix by Archon · aggressive mode · fixes pushed to fix/issue-1273-sync-workspace-respects-default-branch

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/core/src/orchestrator/orchestrator.test.ts (1)

671-693: Nit: typo codbaseWithBranchcodebaseWithBranch.

-      const codbaseWithBranch: Codebase = {
+      const codebaseWithBranch: Codebase = {
         ...mockCodebase,
         default_branch: 'develop',
         default_cwd: '/home/test/.archon/workspaces/owner/repo/source',
       };
       mockGetOrCreateConversation.mockResolvedValue({
         ...mockConversationWithProject,
         codebase_id: 'codebase-789',
       });
-      mockGetCodebase.mockResolvedValue(codbaseWithBranch);
+      mockGetCodebase.mockResolvedValue(codebaseWithBranch);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/orchestrator/orchestrator.test.ts` around lines 671 - 693,
The test declares a variable misspelled as codbaseWithBranch; rename it to
codebaseWithBranch everywhere in the test (the variable declaration and its use
in mockGetCodebase.mockResolvedValue) so references are consistent; ensure the
object still matches the Codebase shape used by the test and update any other
occurrences of codbaseWithBranch within this test (e.g., the local variable
passed to mockGetCodebase) to the corrected identifier.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/orchestrator/orchestrator.test.ts`:
- Around line 134-138: Current use of mock.module('@archon/git', ...) replaces
the module globally and can clash with spy-based mocks in clone.test.ts; replace
this with spyOn-based mocks: spyOn the specific exported functions used in
orchestrator.test.ts (e.g., syncWorkspace, toRepoPath, toBranchName) to return
mockSyncWorkspace, mockToRepoPath, mockToBranchName respectively, and
restore/clear those spies in afterEach; alternatively, if changing to spyOn is
not possible, move this suite to run in its own bun test invocation to avoid
cross-test pollution.

---

Nitpick comments:
In `@packages/core/src/orchestrator/orchestrator.test.ts`:
- Around line 671-693: The test declares a variable misspelled as
codbaseWithBranch; rename it to codebaseWithBranch everywhere in the test (the
variable declaration and its use in mockGetCodebase.mockResolvedValue) so
references are consistent; ensure the object still matches the Codebase shape
used by the test and update any other occurrences of codbaseWithBranch within
this test (e.g., the local variable passed to mockGetCodebase) to the corrected
identifier.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 67aa4020-b7d1-4f59-8d8e-333d2376d534

📥 Commits

Reviewing files that changed from the base of the PR and between bc5a201 and c0fcdd6.

📒 Files selected for processing (4)
  • packages/core/src/db/codebases.test.ts
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/handlers/clone.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/core/src/db/codebases.test.ts

Comment thread packages/core/src/orchestrator/orchestrator.test.ts
@leex279
Copy link
Copy Markdown
Collaborator Author

leex279 commented Apr 17, 2026

PR Review — #1274: fix(core): pass configured default_branch to syncWorkspace

Reviewed: 2026-04-17 | Status: ✅ Auto-fixes applied and pushed


Pre-Review Status

Check Status
Merge Conflicts ✅ None
CI ✅ All 3 checks pass
Behind Base ⚠️ 3 commits behind dev (minor)
Draft ✅ Ready
Size ✅ Normal (10 files, +207/-10)

Summary

This PR correctly identifies and fixes the root cause: syncWorkspace was always called with baseBranch=undefined, causing git reset --hard origin/main on every message for managed clones. The fix — persisting the detected post-clone branch and threading it through to syncWorkspace — is correct and well-scoped.

Four issues were found and auto-fixed directly on the PR branch (commit 3df8d069).


Issues Found and Fixed

🔴 CRITICAL — Migration backfills 'main' for all existing rows

File: migrations/022_add_default_branch_to_codebases.sql

ADD COLUMN IF NOT EXISTS default_branch TEXT DEFAULT 'main' materializes 'main' for every existing row in remote_agent_codebases. For any user whose managed clone is on a feature branch when they run this upgrade migration, the next orchestrator sync will do git reset --hard origin/main — the exact bug this PR aims to fix.

Fix applied: Removed DEFAULT 'main'. Existing rows now get NULL, which falls back to the auto-detect path (pre-existing behavior — no regression, and no destructive change).

-  ADD COLUMN IF NOT EXISTS default_branch TEXT DEFAULT 'main';
+  ADD COLUMN IF NOT EXISTS default_branch TEXT;

Note: 000_combined.sql keeps DEFAULT 'main' for fresh installs. Since createCodebase() now always passes an explicit value, the column default is never used in practice — this is fine.


🟠 HIGH — makeCodebase() missing default_branch in two test helpers (TypeScript type error)

Files:

  • packages/core/src/orchestrator/orchestrator-agent.test.tsmakeCodebase() (line 204) and makeCodebaseForSync() (line 827)
  • packages/core/src/orchestrator/orchestrator-isolation.test.tsmakeCodebase() (line 174)

Adding default_branch: string | null as a required field to the Codebase interface (done in packages/core/src/types/index.ts) breaks TypeScript compilation in these test helpers which return incomplete Codebase objects. bun run type-check would fail on these.

Fix applied: Added default_branch: null (and filled in other missing required fields) to all three helpers.


Issues NOT Fixed (out of scope per investigation.md)

🟠 MEDIUM — Null default_branch for /register-project path

CodeRabbit raised that codebases registered via /register-project never set default_branch, so they'll get NULL after migration and fall back to auto-detect (which may resolve to main). This is a pre-existing limitation and explicitly out of scope per the investigation artifact for this PR. A follow-up issue should track this.

🟡 LOW — mock.module('@archon/git') in orchestrator.test.ts

CodeRabbit flagged potential mock pollution with clone.test.ts. However, this is a false positive: the project's package.json test script already runs each orchestrator test file in its own bun test invocation (... && bun test src/orchestrator/orchestrator.test.ts && bun test src/orchestrator/orchestrator-agent.test.ts && ...), so there's no cross-file module cache pollution. No fix needed.


Code Quality Assessment

Area Verdict
Root cause fix ✅ Correct — toBranchName(codebase.default_branch) passed to syncWorkspace
Branch detection ✅ Correct — uses git rev-parse --abbrev-ref HEAD post-clone, non-fatal
DB layer createCodebase now accepts and persists default_branch
API schema codebase.schemas.ts updated; OpenAPI spec will reflect default_branch
Test coverage ✅ New tests cover: detected branch stored, detached HEAD, rev-parse failure, syncWorkspace branch forwarding
Error handling ✅ Branch detection is non-fatal (falls back to NULL/auto-detect)

Verification

After pushing fixes, type-check and tests pass:

  • bun run type-check ✅ All 10 packages pass
  • bun test packages/core/src/orchestrator/orchestrator.test.ts ✅ 70/70 pass
  • bun test packages/core/src/orchestrator/orchestrator-agent.test.ts ✅ 97/97 pass
  • bun test packages/core/src/orchestrator/orchestrator-isolation.test.ts ✅ 2/2 pass

Archon and others added 4 commits April 17, 2026 16:06
… 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
…anch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…base 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 <noreply@anthropic.com>
@leex279 leex279 force-pushed the fix/issue-1273-sync-workspace-respects-default-branch branch from 3df8d06 to abb6481 Compare April 17, 2026 16:09
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/core/src/handlers/clone.ts (1)

288-293: Consider aligning the timeout with getDefaultBranch (10s).

getDefaultBranch in packages/git/src/branch.ts uses a 10s timeout for a similar local git metadata query. rev-parse --abbrev-ref HEAD is a cheap local read, but on cold I/O (CI, spinning disks, constrained containers) 5s has occasionally been reported as flaky. Since a failure here silently stores NULL and defers to runtime auto-detection, it's not a correctness bug — but using a consistent, slightly more tolerant timeout avoids an unnecessary miss that permanently leaves default_branch NULL for this codebase.

♻️ Optional adjustment
-      ['-C', targetPath, 'rev-parse', '--abbrev-ref', 'HEAD'],
-      { timeout: 5000 }
+      ['-C', targetPath, 'rev-parse', '--abbrev-ref', 'HEAD'],
+      { timeout: 10000 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/handlers/clone.ts` around lines 288 - 293, The timeout for
the local git metadata exec in the clone handler is currently 5000ms and should
be aligned with getDefaultBranch's 10000ms to avoid flaky failures; update the
execFileAsync call that runs 'git -C <targetPath> rev-parse --abbrev-ref HEAD'
(the invocation in the clone handler where stdout is read) to use a 10000ms
timeout instead of 5000ms so the behavior matches getDefaultBranch's allowance
for cold I/O.
packages/core/src/handlers/clone.test.ts (1)

306-368: Good coverage of the branch-detection behavior.

All four cases the reviewer flagged are now exercised (detected branch forwarded, detached HEAD → undefined, rev-parse rejection → non-throwing + undefined, empty stdout → undefined), and assertions check the actual createCodebase argument rather than only the public return shape.

One small robustness note: the args[2] === 'rev-parse' matcher relies on the exact positional layout (-C, <path>, rev-parse, ...). If someone later refactors to ['rev-parse', '--abbrev-ref', 'HEAD'] without -C, these tests would silently fall through to the default mock and default_branch would be undefined — i.e. the "stores master" test would start failing with a confusing message rather than catching the regression at the matcher. Optionally use args.includes('rev-parse') (consistent with the other mocks in this file, e.g. Line 641, 687, 863).

♻️ Optional — more tolerant matcher
-        if (args[0] === '-C' && args[2] === 'rev-parse') {
+        if (args.includes('rev-parse') && args.includes('--abbrev-ref')) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/handlers/clone.test.ts` around lines 306 - 368, The tests
mock spyExecFileAsync to detect git rev-parse by checking args[2] ===
'rev-parse', which is brittle; update each mockImplementation in the
branch-detection tests (the ones that call spyExecFileAsync used by
cloneRepository and assert mockCreateCodebase calls) to use a tolerant matcher
like args.includes('rev-parse') so the mock triggers regardless of positional
changes and the assertions (createArg.default_branch) remain meaningful.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/core/src/handlers/clone.test.ts`:
- Around line 306-368: The tests mock spyExecFileAsync to detect git rev-parse
by checking args[2] === 'rev-parse', which is brittle; update each
mockImplementation in the branch-detection tests (the ones that call
spyExecFileAsync used by cloneRepository and assert mockCreateCodebase calls) to
use a tolerant matcher like args.includes('rev-parse') so the mock triggers
regardless of positional changes and the assertions (createArg.default_branch)
remain meaningful.

In `@packages/core/src/handlers/clone.ts`:
- Around line 288-293: The timeout for the local git metadata exec in the clone
handler is currently 5000ms and should be aligned with getDefaultBranch's
10000ms to avoid flaky failures; update the execFileAsync call that runs 'git -C
<targetPath> rev-parse --abbrev-ref HEAD' (the invocation in the clone handler
where stdout is read) to use a 10000ms timeout instead of 5000ms so the behavior
matches getDefaultBranch's allowance for cold I/O.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3444641d-8ef1-4d88-a407-96688a3eb492

📥 Commits

Reviewing files that changed from the base of the PR and between 3df8d06 and abb6481.

📒 Files selected for processing (12)
  • migrations/000_combined.sql
  • migrations/022_add_default_branch_to_codebases.sql
  • packages/core/src/db/codebases.test.ts
  • packages/core/src/db/codebases.ts
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/handlers/clone.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/orchestrator/orchestrator-agent.ts
  • packages/core/src/orchestrator/orchestrator-isolation.test.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
  • packages/core/src/types/index.ts
  • packages/server/src/routes/schemas/codebase.schemas.ts
✅ Files skipped from review due to trivial changes (6)
  • migrations/000_combined.sql
  • packages/core/src/orchestrator/orchestrator-agent.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
  • packages/core/src/orchestrator/orchestrator-isolation.test.ts
  • packages/core/src/db/codebases.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • migrations/022_add_default_branch_to_codebases.sql
  • packages/core/src/types/index.ts
  • packages/core/src/db/codebases.ts

@leex279
Copy link
Copy Markdown
Collaborator Author

leex279 commented Apr 17, 2026

🔍 Comprehensive PR Review

PR: #1274 — fix(core): pass configured default_branch to syncWorkspace to prevent hard-reset to wrong branch
Reviewed by: 5 specialized agents (code-review, error-handling, test-coverage, comment-quality, docs-impact)
Date: 2026-04-17


Summary

The core design is sound: non-fatal branch detection after clone, safe syncWorkspace auto-detection fallback, and a migration that deliberately omits DEFAULT to protect existing managed clones. CI is green and the new tests are comprehensive. One HIGH issue requires a fix before merge: existing SQLite users won't get the new column (the default deployment path).

Severity Count
🔴 CRITICAL 0
🟠 HIGH 1
🟡 MEDIUM 1
🟢 LOW 6

🟠 HIGH — Must Fix Before Merge

H-1: Missing migrateColumns() entry — existing SQLite users silently miss the column

📍 packages/core/src/db/adapters/sqlite.ts (migrateColumns, ~line 164)

createSchema() gets default_branch TEXT DEFAULT 'main' for fresh installs, but migrateColumns() has no ALTER TABLE ... ADD COLUMN call. SQLite's CREATE TABLE IF NOT EXISTS is a no-op for existing databases. The majority of Archon users run SQLite (no DATABASE_URL). On upgrade, codebase.default_branch will be undefined from SELECT * — silently preserving the very bug this PR is fixing for all upgraders.

Unlike the allow_env_keys precedent (only matters at write-time), default_branch is read on every discoverAllWorkflows call.

View fix

Add after the sessions block in migrateColumns() (pattern: sqlite.ts:204-217):

// Codebases columns
try {
  const cbCols = this.db.prepare("PRAGMA table_info('remote_agent_codebases')").all() as {
    name: string;
  }[];
  const cbColNames = new Set(cbCols.map(c => c.name));

  if (!cbColNames.has('default_branch')) {
    // NULL for existing rows — syncWorkspace will auto-detect the branch at runtime
    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');
}

🟡 MEDIUM — Should Fix Before Merge

M-1: WorkspaceSyncResult mock returns wrong shape in orchestrator.test.ts

📍 packages/core/src/orchestrator/orchestrator.test.ts:130

The new mockSyncWorkspace returns { fetched: true, reset: false }, but WorkspaceSyncResult is { branch, synced, previousHead, newHead, updated }. Tests pass because syncResult?.updated resolves to undefined (falsy), but the updated === true notification path (sendStructuredEvent call) is never exercised — leaving a meaningful branch untested.

View fix
const mockSyncWorkspace = mock(() =>
  Promise.resolve({
    branch: 'main' as ReturnType<typeof mockToBranchName>,
    synced: true,
    previousHead: 'abc12345',
    newHead: 'abc12345',
    updated: false,
  })
);

🟢 LOW Issues

View 6 low-priority suggestions
# Issue Location Suggestion
L-1 toBranchName missing from @archon/git mock orchestrator-agent.test.ts:189-192 Add toBranchName: mock((b: string) => b) — traps future test authors adding non-null branch tests
L-2 Typo: codbaseWithBranch (missing 'e') orchestrator.test.ts:672 Rename to codebaseWithBranch
L-3 Branch detect failure logged at debug instead of warn clone.ts catch block Upgrade to warn — aids debugging "why did sync reset to wrong branch" without --verbose
L-4 Schema allows whitespace-only default_branch codebase.schemas.ts z.string().trim().min(1).nullable() — whitespace-only passes toBranchName and causes recurring sync warnings
L-5 Catch comment says "store NULL" but TS variable is undefined clone.ts catch block Minor precision nit — recommend leaving as-is, intent is clear
L-6 Migration list in database.md missing entries 021 and 022 packages/docs-web/.../database.md Pre-existing gap; back-fill both entries (two one-line additions)

✅ What's Good

  • Non-fatal branch detection is correctly designed — try/catch around rev-parse stores undefined, syncWorkspace auto-detects at runtime. Comment above the block explains both paths clearly.
  • 4 edge-case tests in clone.test.ts cover all meaningful states: normal branch, detached HEAD (HEAD output), rev-parse failure, empty stdout.
  • Migration 022 deliberately omits DEFAULT — existing rows stay NULL rather than being silently set to 'main'. The migration comment explains the reasoning; this is exactly the right call and prevents future maintainers from "fixing" the absent DEFAULT.
  • HEAD detached state guard correctly filters out the literal string 'HEAD', storing undefined instead of a nonsensical branch name.
  • orchestrator.test.ts mocking is model-quality: mockToBranchName is defined, added to the module mock, cleared in clearAllMocks(), and exercised in both branch-forwarding tests.
  • Truthy guard before toBranchName handles both null and "", avoiding the known BranchName cannot be empty throw.
  • execFileAsync used for rev-parse (not exec), consistent with project conventions.

📋 Suggested Follow-up Issues

Title Priority
Add non-null default_branch forwarding test to orchestrator-agent.test.ts P3
Back-fill migration entries 021 + 022 in database.md P3
Support updating default_branch via /update-project and Web UI P2
registerRepository: detect and persist default_branch like cloneRepository P2

Reviewed by Archon comprehensive-pr-review workflow · 5 agents · Artifacts: /.archon/workspaces/coleam00/Archon/artifacts/runs/b8f0f7fd-9582-4226-929e-e15ebe2359b7/review/

… 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 <noreply@anthropic.com>
@leex279
Copy link
Copy Markdown
Collaborator Author

leex279 commented Apr 17, 2026

Code review fixes applied (commit b1f93a6)

H-1 — SQLite migrateColumns() entry for default_branch

Added a Codebases columns block in migrateColumns() in packages/core/src/db/adapters/sqlite.ts. Uses the same PRAGMA table_info pattern as the sessions block. The column is added as TEXT with no DEFAULT so existing rows are NULL, letting syncWorkspace auto-detect the branch.

M-1 — WorkspaceSyncResult mock shape in orchestrator.test.ts

Fixed mockSyncWorkspace to return the correct WorkspaceSyncResult shape:

{ branch: 'main' as BranchName, synced: true, previousHead: 'abc12345', newHead: 'abc12345', updated: false }

Also added import type { BranchName } from '@archon/git' for the type cast.

Validation

  • bun run type-check: All 10 packages pass
  • Tests: 70/70 pass in orchestrator.test.ts
  • No new lint errors introduced (32 pre-existing errors in the pi provider are unrelated to these changes)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

syncWorkspace always resets managed source clone to origin/main, ignoring configured default_branch

1 participant