Skip to content

fix(core): resolve default assistant via config + folder detection on every codebase registration#1729

Merged
Wirasm merged 4 commits into
devfrom
fix/registration-provider-fallback-1580
May 21, 2026
Merged

fix(core): resolve default assistant via config + folder detection on every codebase registration#1729
Wirasm merged 4 commits into
devfrom
fix/registration-provider-fallback-1580

Conversation

@Wirasm
Copy link
Copy Markdown
Collaborator

@Wirasm Wirasm commented May 20, 2026

Summary

  • Problem: New codebases registered via the GitHub / GitLab / Gitea forge adapters were silently created with ai_assistant_type='claude' regardless of the user's configured assistant. clone.ts already did the right resolution (config + SDK folder detection), but the three forge createCodebase call sites passed no ai_assistant_type at all. Issue Project registration falls back to Claude instead of configured Pi provider #1580.
  • Why it matters: A user with assistant: pi in .archon/config.yaml who first interacted with Archon by @archon-mentioning on a GitHub PR would get claude bound to that codebase forever, with no UI surface to discover or fix the mismatch.
  • What changed: Extracted a resolveDefaultAssistant(repoPath) helper into packages/core/src/config/resolve-assistant.ts with the precedence used by clone.ts (.codex / .claude folder → loadConfig().assistant → first built-in provider → 'claude'). clone.ts and the three forge adapters now call the helper.
  • What did not change (scope boundary): createCodebase stays a thin DB function with the simple ?? 'claude' fallback — no dynamic-import config-loading inside the DB layer. The orchestrator path (which already resolved via loadConfig before calling createCodebase) is untouched.

UX Journey

Before

```
User Forge adapter DB
──── ───────────── ──
@archon on PR ────────────▶ createCodebase({
name, repo_url,
default_cwd
}) ────────────────────▶ INSERT ai_assistant_type='claude' ← always

(silently ignores config.assistant)
```

After

```
User Forge adapter resolve-assistant DB
──── ───────────── ───────────────── ──
@archon on PR ────────────▶ resolveDefaultAssistant ─▶ .codex? .claude?
loadConfig().assistant?
first builtIn provider? ai_assistant_type=resolved ← respects user config
createCodebase({ 'claude'
ai_assistant_type: ─▶
resolved
}) ─────────────────────────────────────────────▶ INSERT
```

Architecture Diagram

Before

```
clone.ts ─── inline 27-line block ──▶ getRegisteredProviders + loadConfig + access(.codex/.claude)
github/adapter.ts ───────────────────▶ createCodebase (no ai_assistant_type)
gitlab/adapter.ts ───────────────────▶ createCodebase (no ai_assistant_type)
gitea/adapter.ts ───────────────────▶ createCodebase (no ai_assistant_type)
```

After

```
clone.ts ──────────────────[]──▶ resolveDefaultAssistant() ─[+]──▶ createCodebase({ ai_assistant_type })
github/adapter.ts ─────────[
]──▶ resolveDefaultAssistant() ─[+]──▶ createCodebase({ ai_assistant_type })
gitlab/adapter.ts ─────────[]──▶ resolveDefaultAssistant() ─[+]──▶ createCodebase({ ai_assistant_type })
gitea/adapter.ts ─────────[
]──▶ resolveDefaultAssistant() ─[+]──▶ createCodebase({ ai_assistant_type })
resolve-assistant.ts [+]
resolve-assistant.test.ts [+]
```

Connection inventory:

From To Status Notes
clone.ts resolveDefaultAssistant new replaces 27-line inline block
forge/github/adapter.ts resolveDefaultAssistant new previously passed no ai_assistant_type
community/forge/gitlab/adapter.ts resolveDefaultAssistant new same as github
community/forge/gitea/adapter.ts resolveDefaultAssistant new same as github
resolve-assistant.ts @archon/providers new (lazy) dynamic import inside function body to avoid eager SDK chain load at adapter import time
db/codebases.ts @archon/providers unchanged reverted to dev's simple ?? 'claude' fallback
db/codebases.ts config-loader unchanged reverted; no dynamic import in DB layer

Label Snapshot

  • Risk: risk: low
  • Size: size: M
  • Scope: core
  • Module: core:config, adapters:github, adapters:gitlab, adapters:gitea

Test plan

```bash
bun run type-check # clean
bun run lint # clean (--max-warnings 0)
bun run format:check # clean
bun run test # only pre-existing macOS telegram-markdown failures (unrelated, present on dev)
```

  • Evidence: exact CI batch `bun test src/db/adapters/sqlite.test.ts src/db/codebases.test.ts ... src/config/ src/state/` → 366 pass / 0 fail
  • 8 new unit tests in resolve-assistant.test.ts cover: SDK folder detection, config preference, registry fallback, hardcoded 'claude' fallback, folder-vs-config precedence

Security Impact

  • New permissions/capabilities? No
  • New external network calls? No
  • Secrets/tokens handling changed? No
  • File system access scope changed? No (same access() calls on .codex / .claude that clone.ts already did)

Compatibility / Migration

  • Backward compatible? Yes — codebases already in the DB are unchanged; only NEW codebases pick the resolved assistant.
  • Config/env changes? No
  • Database migration needed? No

Human Verification

  • Verified scenarios: ran the exact CI batched test invocation locally on macOS (366 pass / 0 fail); manually traced the import chain that broke adapter tests on Linux (BUNDLED_IS_BINARY missing from mocked @archon/paths) and confirmed the dynamic-import fix preserves the original clone.ts lazy-loading pattern.
  • Edge cases: empty registry + loadConfig failure → hardcoded 'claude'; SDK folder always wins over configured assistant; community-only registry → 'claude'.
  • What was not verified: actual forge-adapter end-to-end (no GitHub PR test fixture in this repo), but the helper is exercised by the existing clone.test.ts integration tests.

Side Effects / Blast Radius

  • Affected subsystems/workflows: codebase registration on first contact (clone + 3 forge adapters).
  • Potential unintended effects: a user who had a .codex folder in their forge-cloned repo but assistant: claude in config will now get codex (the folder wins). This matches existing clone.ts behavior; the forge paths just lacked the resolution entirely.
  • Guardrails: existing log lines (`assistant_detected_codex`, `assistant_default_from_config`) surface which branch fired.

Rollback Plan

Risks and Mitigations

  • Risk: Tests in the same bun test batch that mock @archon/paths could be affected by the new @archon/providers import chain.
    • Mitigation: Resolved by lazy-loading @archon/providers inside the helper function body (matching the original clone.ts pattern). Verified locally that @archon/adapters and @archon/server test packages run clean.

Credit

This work supersedes #1700 (closed). Thanks to @kagura-agent for the initial investigation and the diagnosis that surfaced the underlying mock.module pollution on Linux. The config-loader.test.ts switch to mocking fs/promises (instead of mock.module('./config-loader')) is preserved from their work — it's a real cross-platform improvement.

Closes #1580.

Summary by CodeRabbit

  • New Features

    • Automatic default AI assistant resolution when registering or creating repositories; adapters now apply the resolved assistant to new codebases.
    • Assistant resolution prioritizes repository SDK markers (if present) and falls back to config or built-in providers.
  • Configuration

    • Added and re-exported a shared assistant-resolution utility for consistent behavior across flows.
  • Tests

    • New and updated tests covering assistant resolution, SDK-folder detection, and config-file behaviors.

Review Change Stack

… every codebase registration

## Summary
- Extract `resolveDefaultAssistant(repoPath)` helper into `packages/core/src/config/resolve-assistant.ts` with precedence: `.codex` / `.claude` folder → `loadConfig().assistant` → first built-in provider → `'claude'`.
- Call the helper from `clone.ts` (replacing the inline block) and from the three forge adapters (`github`, `gitlab`, `gitea`) which previously passed no `ai_assistant_type` and silently defaulted to `'claude'` regardless of the configured assistant.
- `createCodebase` stays a thin DB function with the simple `?? 'claude'` fallback. No dynamic-import config-loading inside the DB layer.
- Lazy-load `@archon/providers` inside the helper so the resolve module doesn't pull provider SDK chains at every adapter import site (which previously broke adapter tests that mock `@archon/paths` without `BUNDLED_IS_BINARY`).
- New `resolve-assistant.test.ts` uses `spyOn` (not `mock.module`) for `loadConfig` and `getRegisteredProviders` so the spies cleanly `mockRestore()` and do not pollute `config-loader.test.ts` running in the same batch.
- `config-loader.test.ts` switches its file I/O mocks from `mock.module('./config-loader', ...)` to `mock.module('fs/promises', ...)` for cross-Bun-version compatibility.
- New `clone.test.ts` cases verify the configured-provider and loadConfig-failure fallbacks via the integration path.

Fixes #1580.

## Test plan
- [x] `bun test src/db/adapters/sqlite.test.ts src/db/codebases.test.ts ... src/config/ src/state/` — exact CI batch, 366 pass / 0 fail
- [x] `bun --filter @archon/core --filter @archon/adapters --filter @archon/server test` — only pre-existing macOS-only telegram-markdown failures (unrelated, present on dev)
- [x] `bun run type-check`, `bun run lint`, `bun run format:check` all clean
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

Adds resolveDefaultAssistant(repoPath) with precedence (.codex → .claude → repo config → first built-in provider → 'claude'); re-exports it; clone handler and GitHub/GitLab/Gitea adapters call it to set ai_assistant_type; tests and config-loader tests updated to mock fs/config IO.

Changes

Assistant Resolution Feature

Layer / File(s) Summary
Test Infrastructure Refactoring
packages/core/src/config/config-loader.test.ts
Config loader tests migrate from mocking config helpers to mocking fs/promises (readFile, writeFile, mkdir) so config I/O is intercepted consistently for resolution tests.
Core Assistant Resolution Logic
packages/core/src/config/resolve-assistant.ts, packages/core/src/config/resolve-assistant.test.ts, packages/core/src/config/index.ts, packages/core/package.json
New resolveDefaultAssistant(repoPath) checks .codex then .claude, then loadConfig(repoPath).assistant, else picks the first built-in provider id or 'claude'. Adds logger, tests, re-export, and package export entry.
Handler Refactoring & Tests
packages/core/src/handlers/clone.ts, packages/core/src/handlers/clone.test.ts
registerRepoAtPath now calls resolveDefaultAssistant(targetPath) for suggested assistant; tests add loadConfig mocks and cases for config-driven and fallback behavior.
Adapter Integration
packages/adapters/src/forge/github/adapter.ts, packages/adapters/src/community/forge/gitlab/adapter.ts, packages/adapters/src/community/forge/gitea/adapter.ts
Adapters call await resolveDefaultAssistant(canonicalPath) when creating new codebase records and set ai_assistant_type accordingly.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 I hopped through folders, short and spry,
Found .codex first beneath the sky,
If none, I read the repo's tune,
Or asked built-ins under the moon,
Now adapters pick the assistant right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% 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 change: extracting a helper to resolve the default assistant via config and folder detection and applying it on every codebase registration across clone and adapters.
Description check ✅ Passed The description is comprehensive and well-structured, covering the problem, why it matters, what changed, scope boundaries, UX journeys, architecture diagrams, test results, security/compatibility analysis, and rollback plan.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/registration-provider-fallback-1580

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

🧹 Nitpick comments (1)
packages/core/src/config/resolve-assistant.test.ts (1)

64-70: ⚡ Quick win

Add an assertion that loadConfig receives the repository path.

Current tests validate outcomes but not the call contract, so a regression to loadConfig() (without path) can slip through undetected.

Also applies to: 96-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/config/resolve-assistant.test.ts` around lines 64 - 70, The
test currently asserts only the return value of resolveDefaultAssistant but not
that configLoader.loadConfig was invoked with the repository path; update the
test(s) (the one using spyLoadConfig and resolveDefaultAssistant and the similar
test at lines 96-105) to assert that spyLoadConfig was called with the repo path
string (e.g., '/repo')—use the existing spyLoadConfig mock to verify the call
contract after calling resolveDefaultAssistant so regressions that omit passing
the path to configLoader.loadConfig are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/config/resolve-assistant.ts`:
- Around line 26-40: The current detection logic swallows all errors when
probing codexFolder and claudeFolder using access, which hides non-ENOENT issues
(e.g., EACCES, EIO); update each catch to inspect the caught error (e) and only
continue when e.code === 'ENOENT', otherwise rethrow or wrap with a clear
message that probing failed for codexFolder/claudeFolder; reference the async
probes using access(...) and the logged contexts getLog().debug({ path:
codexFolder }, 'assistant_detected_codex') and getLog().debug({ path:
claudeFolder }, 'assistant_detected_claude') so the error handling change is
applied around those specific try/catch blocks.
- Around line 42-47: The config loader is called without the repository path so
per-repo config (.archon/config.yaml) is ignored; update the call in
resolve-assistant (where loadConfig() is invoked) to pass the repoPath argument
(i.e., use loadConfig(repoPath)) so repo-level assistant settings are
considered, and adjust any surrounding code/imports if needed to match the
loadConfig signature.

In `@packages/core/src/handlers/clone.test.ts`:
- Around line 804-815: The test is flaky because provider resolution depends on
the runtime provider ordering; update the test to stub the provider registry so
'claude' is deterministically chosen (for example mock the '`@archon/providers`'
export or the getProviders/getBuiltInProviders function used by cloneRepository
to return an array with a provider object for ai_assistant_type: 'claude' as the
first element), then run the same steps (mockLoadConfig rejection, spyFsAccess
rejection, mockCreateCodebase resolution) and assert createCodebase was called
with ai_assistant_type 'claude'; ensure the mock is restored/cleared after the
test to avoid affecting other tests.

---

Nitpick comments:
In `@packages/core/src/config/resolve-assistant.test.ts`:
- Around line 64-70: The test currently asserts only the return value of
resolveDefaultAssistant but not that configLoader.loadConfig was invoked with
the repository path; update the test(s) (the one using spyLoadConfig and
resolveDefaultAssistant and the similar test at lines 96-105) to assert that
spyLoadConfig was called with the repo path string (e.g., '/repo')—use the
existing spyLoadConfig mock to verify the call contract after calling
resolveDefaultAssistant so regressions that omit passing the path to
configLoader.loadConfig are caught.
🪄 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: e8dcb386-392c-4fe4-92a7-ef5c54e36b95

📥 Commits

Reviewing files that changed from the base of the PR and between 5283ea9 and c260e69.

📒 Files selected for processing (9)
  • packages/adapters/src/community/forge/gitea/adapter.ts
  • packages/adapters/src/community/forge/gitlab/adapter.ts
  • packages/adapters/src/forge/github/adapter.ts
  • packages/core/src/config/config-loader.test.ts
  • packages/core/src/config/index.ts
  • packages/core/src/config/resolve-assistant.test.ts
  • packages/core/src/config/resolve-assistant.ts
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/handlers/clone.ts

Comment on lines +26 to +40
try {
await access(codexFolder);
getLog().debug({ path: codexFolder }, 'assistant_detected_codex');
return 'codex';
} catch {
// fall through
}

try {
await access(claudeFolder);
getLog().debug({ path: claudeFolder }, 'assistant_detected_claude');
return 'claude';
} catch {
// fall through
}
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 | ⚡ Quick win

Do not swallow non-ENOENT probe failures during SDK folder detection.

Lines 30 and 38 catch all errors and continue. Permission or I/O failures should not silently fall through to a different assistant; that masks unsafe states and makes behavior misleading.

Suggested fix
   try {
     await access(codexFolder);
     getLog().debug({ path: codexFolder }, 'assistant_detected_codex');
     return 'codex';
-  } catch {
-    // fall through
+  } catch (error) {
+    const err = error as NodeJS.ErrnoException;
+    if (err.code !== 'ENOENT') throw error;
   }

   try {
     await access(claudeFolder);
     getLog().debug({ path: claudeFolder }, 'assistant_detected_claude');
     return 'claude';
-  } catch {
-    // fall through
+  } catch (error) {
+    const err = error as NodeJS.ErrnoException;
+    if (err.code !== 'ENOENT') throw error;
   }

As per coding guidelines, "Throw early with clear errors for unsupported or unsafe states; never silently swallow errors or broaden permissions".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
await access(codexFolder);
getLog().debug({ path: codexFolder }, 'assistant_detected_codex');
return 'codex';
} catch {
// fall through
}
try {
await access(claudeFolder);
getLog().debug({ path: claudeFolder }, 'assistant_detected_claude');
return 'claude';
} catch {
// fall through
}
try {
await access(codexFolder);
getLog().debug({ path: codexFolder }, 'assistant_detected_codex');
return 'codex';
} catch (error) {
const err = error as NodeJS.ErrnoException;
if (err.code !== 'ENOENT') throw error;
}
try {
await access(claudeFolder);
getLog().debug({ path: claudeFolder }, 'assistant_detected_claude');
return 'claude';
} catch (error) {
const err = error as NodeJS.ErrnoException;
if (err.code !== 'ENOENT') throw error;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/config/resolve-assistant.ts` around lines 26 - 40, The
current detection logic swallows all errors when probing codexFolder and
claudeFolder using access, which hides non-ENOENT issues (e.g., EACCES, EIO);
update each catch to inspect the caught error (e) and only continue when e.code
=== 'ENOENT', otherwise rethrow or wrap with a clear message that probing failed
for codexFolder/claudeFolder; reference the async probes using access(...) and
the logged contexts getLog().debug({ path: codexFolder },
'assistant_detected_codex') and getLog().debug({ path: claudeFolder },
'assistant_detected_claude') so the error handling change is applied around
those specific try/catch blocks.

Comment thread packages/core/src/config/resolve-assistant.ts
Comment on lines +804 to +815
test('falls back to claude when loadConfig fails', async () => {
mockLoadConfig.mockRejectedValue(new Error('config load failed'));
spyFsAccess.mockRejectedValue(Object.assign(new Error('ENOENT'), { code: 'ENOENT' }));
mockCreateCodebase.mockResolvedValueOnce(
makeCodebase({ ai_assistant_type: 'claude' }) as ReturnType<typeof makeCodebase>
);

await cloneRepository('https://github.com/owner/repo');

const createCall = mockCreateCodebase.mock.calls[0] as [{ ai_assistant_type: string }];
expect(createCall[0].ai_assistant_type).toBe('claude');
});
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 | 🟡 Minor | ⚡ Quick win

Make the fallback test deterministic by controlling provider registry.

Line 814 asserts 'claude', but resolver falls back to first built-in provider before hardcoded 'claude'. Without mocking @archon/providers, this test depends on runtime provider ordering.

As per coding guidelines, "keep tests deterministic with no flaky timing or network dependence without guardrails".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/handlers/clone.test.ts` around lines 804 - 815, The test is
flaky because provider resolution depends on the runtime provider ordering;
update the test to stub the provider registry so 'claude' is deterministically
chosen (for example mock the '`@archon/providers`' export or the
getProviders/getBuiltInProviders function used by cloneRepository to return an
array with a provider object for ai_assistant_type: 'claude' as the first
element), then run the same steps (mockLoadConfig rejection, spyFsAccess
rejection, mockCreateCodebase resolution) and assert createCodebase was called
with ai_assistant_type 'claude'; ensure the mock is restored/cleared after the
test to avoid affecting other tests.

config-loader.ts eagerly imports @archon/providers (for runtime validation
of the configured assistant ID against the registry), which transitively
loads claude/codex binary-resolvers and their BUNDLED_IS_BINARY dependency
on @archon/paths. Static-importing loadConfig at module top therefore
forces every caller of resolve-assistant — including the three forge
adapters — to pull that chain too. Adapter tests that mock @archon/paths
without BUNDLED_IS_BINARY then break on Linux.

Move the loadConfig import to a dynamic import inside the function body
alongside the getRegisteredProviders one. Nothing in this module is
loaded eagerly anymore.
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.

♻️ Duplicate comments (2)
packages/core/src/config/resolve-assistant.ts (2)

25-31: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle non-ENOENT probe errors explicitly.

Line 29 and Line 37 currently swallow all access(...) failures. If probe errors are from permissions or I/O (not missing folders), assistant resolution should fail instead of silently falling through.

Proposed fix
   try {
     await access(codexFolder);
     getLog().debug({ path: codexFolder }, 'assistant_detected_codex');
     return 'codex';
-  } catch {
-    // fall through
+  } catch (error) {
+    const err = error as NodeJS.ErrnoException;
+    if (err.code !== 'ENOENT') throw error;
   }

   try {
     await access(claudeFolder);
     getLog().debug({ path: claudeFolder }, 'assistant_detected_claude');
     return 'claude';
-  } catch {
-    // fall through
+  } catch (error) {
+    const err = error as NodeJS.ErrnoException;
+    if (err.code !== 'ENOENT') throw error;
   }

Also applies to: 33-39

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/config/resolve-assistant.ts` around lines 25 - 31, The
try/catch blocks that call access(codexFolder) and access(howtoFolder) currently
swallow all errors; update both to only ignore errors with error.code ===
'ENOENT' and rethrow (or propagate) others so permission or I/O errors fail
fast. Concretely, inside the catch for the codexFolder and howtoFolder probes,
inspect the caught error (e) and if e?.code !== 'ENOENT' throw e (or return a
failing result) otherwise continue falling through; keep getLog().debug and the
existing 'codex'/'howto' returns unchanged.

48-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use repoPath when loading config.

Line 49 calls loadConfig() without the target repository path, so repo-specific assistant config can be skipped during registration.

Proposed fix
-    const config = await loadConfig();
+    const config = await loadConfig(repoPath);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/config/resolve-assistant.ts` around lines 48 - 50, The call
to loadConfig() in resolve-assistant.ts ignores the repository path so
repo-specific assistant config may be missed; update the loadConfig invocation
(the loadConfig function import and its usage) to pass the repoPath argument
(e.g., loadConfig(repoPath)) and ensure repoPath is available in the scope where
resolve-assistant determines config.assistant so repository-specific settings
are loaded during assistant registration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/core/src/config/resolve-assistant.ts`:
- Around line 25-31: The try/catch blocks that call access(codexFolder) and
access(howtoFolder) currently swallow all errors; update both to only ignore
errors with error.code === 'ENOENT' and rethrow (or propagate) others so
permission or I/O errors fail fast. Concretely, inside the catch for the
codexFolder and howtoFolder probes, inspect the caught error (e) and if e?.code
!== 'ENOENT' throw e (or return a failing result) otherwise continue falling
through; keep getLog().debug and the existing 'codex'/'howto' returns unchanged.
- Around line 48-50: The call to loadConfig() in resolve-assistant.ts ignores
the repository path so repo-specific assistant config may be missed; update the
loadConfig invocation (the loadConfig function import and its usage) to pass the
repoPath argument (e.g., loadConfig(repoPath)) and ensure repoPath is available
in the scope where resolve-assistant determines config.assistant so
repository-specific settings are loaded during assistant registration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 736dd089-98e5-4cec-b1db-6441c169c459

📥 Commits

Reviewing files that changed from the base of the PR and between c260e69 and c6d649b.

📒 Files selected for processing (1)
  • packages/core/src/config/resolve-assistant.ts

Wirasm added 2 commits May 20, 2026 12:52
loadConfig() without a path only merges the global config; the repo's
own .archon/config.yaml (which can set assistant: pi at the project
level) is silently skipped. Pass repoPath so repo-level config is
honored during registration. Add a call-contract assertion in
resolve-assistant.test.ts so a future regression that drops the path
is caught.

Surfaced by CodeRabbit review on #1729.
…fig-loader chain

@archon/core/config/index.ts does `export * from './config-loader'`, which
forces eager loading of config-loader.ts (and its top-level @archon/providers
import) at every site that imports from @archon/core/config. The three forge
adapters were importing the helper through that barrel, which pulled in the
binary-resolver chain and broke @archon/adapters tests on Linux (mocked
@archon/paths without BUNDLED_IS_BINARY).

Add a dedicated './config/resolve-assistant' subpath export and switch the
three forge adapters to it. Only resolve-assistant.ts is loaded — no
transitive @archon/providers at module-load time.
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.

Caution

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

⚠️ Outside diff range comments (3)
packages/adapters/src/community/forge/gitlab/adapter.ts (1)

596-596: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Assistant resolution runs too early for new registrations.

Line 596 calls resolveDefaultAssistant(canonicalPath) before the repository is cloned. For new codebases, checked-in SDK folders and repo config cannot be detected yet, so fallback assistant selection is used unexpectedly. Move resolution after clone, or recalculate and persist it once clone completes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/src/community/forge/gitlab/adapter.ts` at line 596, The
call to resolveDefaultAssistant(canonicalPath) occurs too early (before the
repository is cloned) causing incorrect assistant selection for new repos; move
the resolution so it happens after the clone completes (or re-run and persist it
immediately after clone finishes). Specifically, locate the code that performs
the repo clone (the clone/checkout routine used in this adapter) and either
remove the pre-clone resolveDefaultAssistant(canonicalPath) call or add a
follow-up call that recalculates and persists the selected assistant once
cloning is done; ensure you use the same canonicalPath and any repo metadata
produced by the clone to compute the correct assistant and persist that value
back to whatever structure currently holds ai_assistant_type.
packages/adapters/src/forge/github/adapter.ts (1)

624-624: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Pre-clone assistant resolution misses repo-driven signals.

Line 624 resolves assistant type before ensureRepoReady() has cloned the repository. On first-time codebase creation, .codex/.claude and repo config aren’t present yet, so the resolver can’t honor intended precedence. Resolve after clone (or update ai_assistant_type immediately after clone for new codebases).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/src/forge/github/adapter.ts` at line 624, The
ai_assistant_type is being computed too early with
resolveDefaultAssistant(canonicalPath) before the repo is cloned by
ensureRepoReady(), so repo signals like .codex/.claude and repo config are
missed; move the call to resolveDefaultAssistant(canonicalPath) so it runs after
ensureRepoReady() completes (or, if cloning occurs only for new codebases, set
ai_assistant_type once more immediately after the clone/update step), updating
any variable assignment that currently uses ai_assistant_type to use the
post-clone value; target the resolveDefaultAssistant, ensureRepoReady, and
ai_assistant_type sites in the function to implement this change.
packages/adapters/src/community/forge/gitea/adapter.ts (1)

646-646: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Resolve assistant after repository materialization.

Line 646 resolves from canonicalPath before ensureRepoReady() clones the repo. On first-time registrations, .codex/.claude and repo config are not available yet, so assistant selection can still fall through to fallback behavior. Resolve after clone (or recompute and update for new codebases).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/src/community/forge/gitea/adapter.ts` at line 646, The code
calls resolveDefaultAssistant(canonicalPath) too early — move the
ai_assistant_type resolution to after the repository is materialized (after
ensureRepoReady() / the clone completes) so that .codex/.claude and repo config
are available; alternatively, if you must resolve earlier, recompute and update
the stored ai_assistant_type once ensureRepoReady()/materializeRepo completes.
Update the logic around resolveDefaultAssistant, ensureRepoReady, and the field
ai_assistant_type to perform the resolution post-clone (or trigger a re-resolve
and persist the new value for new codebases).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/adapters/src/community/forge/gitea/adapter.ts`:
- Line 646: The code calls resolveDefaultAssistant(canonicalPath) too early —
move the ai_assistant_type resolution to after the repository is materialized
(after ensureRepoReady() / the clone completes) so that .codex/.claude and repo
config are available; alternatively, if you must resolve earlier, recompute and
update the stored ai_assistant_type once ensureRepoReady()/materializeRepo
completes. Update the logic around resolveDefaultAssistant, ensureRepoReady, and
the field ai_assistant_type to perform the resolution post-clone (or trigger a
re-resolve and persist the new value for new codebases).

In `@packages/adapters/src/community/forge/gitlab/adapter.ts`:
- Line 596: The call to resolveDefaultAssistant(canonicalPath) occurs too early
(before the repository is cloned) causing incorrect assistant selection for new
repos; move the resolution so it happens after the clone completes (or re-run
and persist it immediately after clone finishes). Specifically, locate the code
that performs the repo clone (the clone/checkout routine used in this adapter)
and either remove the pre-clone resolveDefaultAssistant(canonicalPath) call or
add a follow-up call that recalculates and persists the selected assistant once
cloning is done; ensure you use the same canonicalPath and any repo metadata
produced by the clone to compute the correct assistant and persist that value
back to whatever structure currently holds ai_assistant_type.

In `@packages/adapters/src/forge/github/adapter.ts`:
- Line 624: The ai_assistant_type is being computed too early with
resolveDefaultAssistant(canonicalPath) before the repo is cloned by
ensureRepoReady(), so repo signals like .codex/.claude and repo config are
missed; move the call to resolveDefaultAssistant(canonicalPath) so it runs after
ensureRepoReady() completes (or, if cloning occurs only for new codebases, set
ai_assistant_type once more immediately after the clone/update step), updating
any variable assignment that currently uses ai_assistant_type to use the
post-clone value; target the resolveDefaultAssistant, ensureRepoReady, and
ai_assistant_type sites in the function to implement this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 672b772e-2dd0-4a2a-adef-229649e71744

📥 Commits

Reviewing files that changed from the base of the PR and between b5cee79 and ded6e06.

📒 Files selected for processing (4)
  • packages/adapters/src/community/forge/gitea/adapter.ts
  • packages/adapters/src/community/forge/gitlab/adapter.ts
  • packages/adapters/src/forge/github/adapter.ts
  • packages/core/package.json

@Wirasm Wirasm merged commit 7ab4df8 into dev May 21, 2026
4 checks passed
@Wirasm Wirasm deleted the fix/registration-provider-fallback-1580 branch May 21, 2026 07:53
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.

Project registration falls back to Claude instead of configured Pi provider

1 participant