Skip to content

Bugfix/fix pi provider#1553

Closed
worldomkar wants to merge 15 commits into
coleam00:devfrom
worldomkar:bugfix/fix-pi-provider
Closed

Bugfix/fix pi provider#1553
worldomkar wants to merge 15 commits into
coleam00:devfrom
worldomkar:bugfix/fix-pi-provider

Conversation

@worldomkar

@worldomkar worldomkar commented May 3, 2026

Copy link
Copy Markdown

Summary

Describe this PR in 2-5 bullets:

  • Problem: "Deprioritise hardcoded 'claude' in favour of process.env.DEFAULT_AI_ASSISTANT"
  • Why it matters: "Supports pi-coding-agent"
  • What changed: "Setup, database schema default, code changes"
  • What did not change (scope boundary):
    • Tests need to be updated, PR code needs to be reviewed before merging.
    • pi provider UI
    • js-yaml was added by "pi" to fix "archon"'s "pi-provider" :D -- needs to be tested if dropping this dependency resurfaces any bugs

Linked Issue

config in ~/.archon/config.yaml that uses pi's model from ~/.pi/agent/models.json

assistants:
  pi: 
    model: macbook/mlx-community/Qwen3.6-35B-A3B-mxfp4
    enableExtensions: true

default_assistant: pi

Summary by CodeRabbit

  • New Features

    • Added support for "pi" as an AI assistant provider option alongside existing choices.
    • Setup wizard now includes configuration for the pi provider.
  • Bug Fixes

    • Improved error handling and auto-recovery for missing conversations during message operations.
    • Enhanced session persistence error handling for edge cases.
  • Improvements

    • Refined assistant provider selection logic with better fallback handling via environment variables.
    • Updated YAML parsing for improved library compatibility.

@coderabbitai

coderabbitai Bot commented May 3, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds Pi as a first-class AI provider option throughout the system. Changes include: updating database schema defaults from hardcoded 'claude' to NULL, extending the setup wizard with Pi detection and multiselect UI, switching YAML parsing from Bun-native to js-yaml, implementing new provider fallback chains (built-in → DEFAULT_AI_ASSISTANT'claude'), and hardening error handling in Pi's event bridge and conversation persistence.

Changes

Pi Provider Support and Assistant Selection Refactoring

Layer / File(s) Summary
Schema & Migration
migrations/000_combined.sql, migrations/001_initial_schema.sql
Database ai_assistant_type columns in remote_agent_codebases and remote_agent_conversations now default to NULL instead of hardcoded 'claude', allowing runtime logic to determine the assistant.
Dependency Additions
packages/core/package.json, packages/server/package.json, packages/workflows/package.json
Added js-yaml (^4.1.0) as a runtime dependency to replace Bun-native YAML parsing across all packages.
YAML Parsing Migration
packages/core/src/config/config-loader.ts, packages/workflows/src/loader.ts, packages/server/src/routes/api.ts
Switched from Bun.YAML (parseYaml(), stringify()) to jsYaml.load() and jsYaml.dump() for consistent YAML serialization.
Config Loader Updates
packages/core/src/config/config-loader.ts
Extended default assistant configuration with Pi provider defaults, updated safe-field allowlist for web exposure to include pi, and changed assistant fallback from hardcoded 'claude' to: first registered built-in provider → DEFAULT_AI_ASSISTANT'claude'.
Setup Wizard Wiring
packages/cli/src/commands/setup.ts
Added pi: boolean to SetupConfig.ai and ExistingConfig.hasPi detection via PI_* env variables. Updated collectAIConfig() to include Pi in multiselect UI, initialize defaultAssistant with new fallback chain, and report Pi in configuration summary.
Database Query Updates
packages/core/src/db/codebases.ts, packages/core/src/db/conversations.ts
createCodebase now loads config and uses config.assistant when ai_assistant_type is not provided. getOrCreateConversation initializes assistantType as undefined, inherits from parent/codebase, and falls back to loadConfig().assistant when still undefined. updateConversationTitle now logs warnings instead of throwing on missing conversation rows.
Clone Handler Default Selection
packages/core/src/handlers/clone.ts
Updated defaultProvider assignment to check first registered built-in provider id before DEFAULT_AI_ASSISTANT and 'claude' fallback.
Documentation Updates
.claude/docs/workflow-yaml-reference.md
Updated provider field to allow `'claude'
Error Handling Improvements
packages/core/src/orchestrator/orchestrator-agent.ts, packages/providers/src/community/pi/event-bridge.ts
tryPersistSessionId now explicitly ignores SessionNotFoundError. Pi's event bridge normalizes non-Error rejection values to Error before queuing, and queue iteration now checks buffer.length > 0 before shifting.
Type Assertion Cleanups
packages/providers/src/claude/provider.ts, packages/providers/src/community/pi/provider.ts, packages/workflows/src/dag-executor.ts
Removed unnecessary explicit type casts on function results (outputFormat, modelUsage, nodeConfig setup, MCP server names, upstream dependencies, and workflow message metadata), relying on type inference instead.
Web Message Persistence
packages/server/src/routes/api.ts
Message handler now auto-creates conversations via getOrCreateConversation('web', conversationId) when persisting messages for a non-existent web conversation ID, with error logging for failed auto-creation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • coleam00/Archon#1526: The PR adds Pi to the setup wizard's multiselect UI and detection logic, directly addressing the missing Pi option in the setup flow.

Possibly related PRs

  • coleam00/Archon#1355: Modifies the same Pi provider implementation files (event-bridge.ts, provider.ts) with related session/error handling changes.
  • coleam00/Archon#1480: Adds workflows and scripts that use provider: pi, building on the Pi provider support infrastructure established in this PR.
  • coleam00/Archon#1297: Also modifies Pi provider event-bridge and provider.ts to enhance error handling and session flows.

Poem

🐰 Pi joins the assistant parade,
With schemas flexing, defaults fade,
js-yaml parses, configs align,
Setup wizards show all three in line,
Error handling stands tall and true,
Type assertions bid adieu!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description partially fills the template with only 4 bullets and a linked issue, but omits most required sections (UX Journey, Architecture Diagram, Label Snapshot, Change Metadata, Validation Evidence, Security Impact, Compatibility/Migration, Human Verification, Side Effects, Rollback Plan, Risks/Mitigations). Complete all required sections of the PR template including validation evidence, security impact, compatibility/migration, human verification, side effects, rollback plan, and risks/mitigations.
Title check ❓ Inconclusive The title 'Bugfix/fix pi provider' is vague and generic, using non-descriptive terms that don't clearly convey the specific change or problem being fixed. Replace with a more specific title that describes the actual bugfix, e.g., 'Deprioritize hardcoded claude in favor of DEFAULT_AI_ASSISTANT to support pi provider selection'.
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 84.00% which is sufficient. The required threshold is 80.00%.
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 unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (2)
packages/providers/src/community/pi/event-bridge.ts (1)

315-351: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize rejected values to real Error objects (avoid err as Error).

bridgeSession currently does queue.push({ kind: 'error', error: err as Error }) in both the subscription listener and promptPromise rejection paths. If Pi rejects with a non-Error value (e.g., string/object), the runtime value thrown via throw item.error may not be an actual Error, which can break stack traces and any downstream error handling that assumes Error.

🛠️ Proposed fix
 export async function* bridgeSession(
   session: AgentSession,
   prompt: string,
   abortSignal?: AbortSignal,
   jsonSchema?: Record<string, unknown>,
   uiBridge?: BridgeNotifier
 ): AsyncGenerator<MessageChunk> {
   const queue = new AsyncQueue<BridgeQueueItem>();
+  const toError = (err: unknown): Error => {
+    if (err instanceof Error) return err;
+    return new Error(typeof err === 'string' ? err : String(err));
+  };
   uiBridge?.setEmitter(chunk => {
     queue.push({ kind: 'chunk', chunk });
   });
   // ...
   const unsubscribe = session.subscribe((event: AgentSessionEvent) => {
     try {
       for (const chunk of mapPiEvent(event)) {
         if (wantsStructured && chunk.type === 'assistant') {
           assistantBuffer += chunk.content;
         }
         queue.push({ kind: 'chunk', chunk });
       }
     } catch (err) {
-      queue.push({ kind: 'error', error: err as Error });
+      queue.push({ kind: 'error', error: toError(err) });
     }
   });

   // ...
   const promptPromise = session.prompt(prompt).then(
     () => {
       queue.push({ kind: 'done' });
     },
     (err: unknown) => {
-      queue.push({ kind: 'error', error: err as Error });
+      queue.push({ kind: 'error', error: toError(err) });
     }
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/community/pi/event-bridge.ts` around lines 315 - 351,
Normalize non-Error rejections before pushing to the queue: where the
subscription catch and the promptPromise rejection currently do queue.push({
kind: 'error', error: err as Error }), replace that pattern by converting the
unknown err into a real Error (e.g., const normalized = err instanceof Error ?
err : new Error(String(err)) ) and push { kind: 'error', error: normalized }
instead; apply this change for the error handler in the session.subscribe
callback and the rejection handler attached to session.prompt (symbols:
session.subscribe, mapPiEvent, queue, promptPromise, session.prompt).
packages/core/src/db/conversations.ts (1)

97-112: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat nullable codebase assistants as unset before the fallback.

remote_agent_codebases.ai_assistant_type is now allowed to be NULL, but Line 104 assigns it directly and the fallback only checks assistantType === undefined. For a codebase row with NULL, this bypasses loadConfig().assistant and inserts a null assistant into the new conversation.

💡 Possible fix
   if (codebaseId) {
     const codebase = await pool.query<{ ai_assistant_type: string }>(
       'SELECT ai_assistant_type FROM remote_agent_codebases WHERE id = $1',
       [codebaseId]
     );
-    if (codebase.rows[0]) {
-      assistantType = codebase.rows[0].ai_assistant_type;
+    const codebaseAssistant = codebase.rows[0]?.ai_assistant_type;
+    if (codebaseAssistant != null) {
+      assistantType = codebaseAssistant;
     }
   }
 
-  if (assistantType === undefined) {
+  if (assistantType == null) {
     const config = await loadConfig();
     assistantType = config.assistant;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/db/conversations.ts` around lines 97 - 112, The code
assigns remote_agent_codebases.ai_assistant_type directly to assistantType but
only falls back when assistantType === undefined, so a NULL DB value becomes a
set null and prevents loadConfig() from supplying the assistant; update the
assignment/check to treat null as unset: when codebase.rows[0] exists only
assign assistantType if codebase.rows[0].ai_assistant_type is not null/undefined
(e.g., check != null or use the nullish-coalescing logic), or change the
fallback to trigger when assistantType is nullish (assistantType == null) and
call loadConfig(); reference variables/functions: assistantType, codebaseId,
pool.query, remote_agent_codebases.ai_assistant_type, and loadConfig.
🧹 Nitpick comments (1)
packages/providers/src/community/pi/event-bridge.ts (1)

69-83: ⚡ Quick win

AsyncQueue should not treat undefined as “buffer empty” (latent generic correctness issue).

AsyncQueue.iterate() uses:

  • const next = this.buffer.shift();
  • if (next !== undefined) { yield next; ... }

Because buffer.shift() returns T | undefined, this conflates “queue item is actually undefined” with “no buffered item”. Since AsyncQueue is generic, this can become a silent correctness bug if it’s ever used with T that allows undefined in the future.

♻️ Proposed fix
   private async *iterate(): AsyncGenerator<T> {
     while (true) {
-      const next = this.buffer.shift();
-      if (next !== undefined) {
-        yield next;
-        continue;
-      }
+      if (this.buffer.length > 0) {
+        // `shift()` can return undefined only when empty; length guard avoids that.
+        const next = this.buffer.shift() as T;
+        yield next;
+        continue;
+      }
       if (this.closed) return;
       const result = await new Promise<IteratorResult<T>>(resolve => {
         this.waiters.push(resolve);
       });
       if (result.done) return;
       yield result.value;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/community/pi/event-bridge.ts` around lines 69 - 83,
AsyncQueue.iterate treats a shifted undefined value as "empty" which breaks
generic correctness; change the check to test buffer length first (e.g., if
(this.buffer.length > 0) { const next = this.buffer.shift()!; yield next;
continue; }) so actual undefined items are yielded; keep the rest of the loop
(this.closed, waiters promise handling and result.done handling) unchanged and
reference AsyncQueue.iterate, this.buffer, this.waiters, and this.closed when
applying the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/docs/workflow-yaml-reference.md:
- Around line 27-29: The documentation currently hardcodes "Claude" in the
Default description string ("**Default**: falls back to `.archon/config.yaml`
assistants default (Claude)"); update that wording to remove the Claude
implication and instead state that the default is taken from the assistants
setting in `.archon/config.yaml` or environment (e.g., "falls back to the
assistants default from `.archon/config.yaml` / environment"), so the doc
reflects that the provider can be Claude, PI, Codex, etc.; edit the line
containing that Default text accordingly.

In `@packages/cli/src/commands/setup.ts`:
- Around line 822-831: The code picks defaultAssistant from the global registry
order instead of from the set of assistants actually selected or from a valid
env value; fix by computing the enabled provider list from
hasClaude/hasCodex/hasPi (e.g., enabledProviders = ['claude','codex','pi']
filtered by those booleans), then set defaultAssistant to
process.env.DEFAULT_AI_ASSISTANT if that value is present in enabledProviders,
otherwise to the first item of enabledProviders if any, otherwise fall back to
process.env.DEFAULT_AI_ASSISTANT only if it exists and is a valid registered
provider, and finally to the original registry fallback
(getRegisteredProviders().find(p => p.builtIn)?.id ?? 'claude'); apply this same
selection logic where defaultAssistant is determined (references: hasClaude,
hasCodex, hasPi, defaultAssistant, getRegisteredProviders(),
process.env.DEFAULT_AI_ASSISTANT).

In `@packages/core/src/db/conversations.ts`:
- Around line 241-255: The updateConversationTitle function currently logs a
warning when no rows are updated; change this to throw a not-found error so
callers relying on ConversationNotFoundError get a failure. In
updateConversationTitle (and its use of pool.query and result.rowCount) replace
the log.warn branch with throwing the appropriate error (e.g.,
ConversationNotFoundError or a standardized RecordNotFound/NotFound error used
across the codebase) including the conversation id so the PATCH route can return
404; ensure you import/reference the same error class used elsewhere for
consistency.

In `@packages/core/src/handlers/clone.ts`:
- Around line 48-52: The current logic that sets defaultProvider (using
getRegisteredProviders().find(...)?id ?? process.env.DEFAULT_AI_ASSISTANT ??
'claude') lets a built-in provider always win and can persist an unvalidated
DEFAULT_AI_ASSISTANT via suggestedAssistant into createCodebase; change it to
prefer process.env.DEFAULT_AI_ASSISTANT when present, validate that value
against getRegisteredProviders() (by matching id), and only fall back to the
first built-in provider or 'claude' if validation fails; additionally, perform a
fail-fast error or normalization before calling createCodebase so createCodebase
never receives an unregistered ai_assistant_type (reuse the same validation
logic used in config-loader.ts to ensure consistency).

In `@packages/server/src/routes/api.ts`:
- Around line 1411-1428: The handler currently swallows errors from
conversationDb.findConversationByPlatformId and
conversationDb.getOrCreateConversation and proceeds to dispatch messages without
a persisted conversation; change this so that if findConversationByPlatformId
throws, bubble the error (or return 500) instead of continuing, and if
getOrCreateConversation throws attempt a single retry of
findConversationByPlatformId (to handle a racing insert) and only if that retry
still yields no conv return/throw a 500 error; update the logic around
conversationDb.findConversationByPlatformId,
conversationDb.getOrCreateConversation and the conv variable to enforce this
flow and log the final failure via getLog().error before rejecting the request.

---

Outside diff comments:
In `@packages/core/src/db/conversations.ts`:
- Around line 97-112: The code assigns remote_agent_codebases.ai_assistant_type
directly to assistantType but only falls back when assistantType === undefined,
so a NULL DB value becomes a set null and prevents loadConfig() from supplying
the assistant; update the assignment/check to treat null as unset: when
codebase.rows[0] exists only assign assistantType if
codebase.rows[0].ai_assistant_type is not null/undefined (e.g., check != null or
use the nullish-coalescing logic), or change the fallback to trigger when
assistantType is nullish (assistantType == null) and call loadConfig();
reference variables/functions: assistantType, codebaseId, pool.query,
remote_agent_codebases.ai_assistant_type, and loadConfig.

In `@packages/providers/src/community/pi/event-bridge.ts`:
- Around line 315-351: Normalize non-Error rejections before pushing to the
queue: where the subscription catch and the promptPromise rejection currently do
queue.push({ kind: 'error', error: err as Error }), replace that pattern by
converting the unknown err into a real Error (e.g., const normalized = err
instanceof Error ? err : new Error(String(err)) ) and push { kind: 'error',
error: normalized } instead; apply this change for the error handler in the
session.subscribe callback and the rejection handler attached to session.prompt
(symbols: session.subscribe, mapPiEvent, queue, promptPromise, session.prompt).

---

Nitpick comments:
In `@packages/providers/src/community/pi/event-bridge.ts`:
- Around line 69-83: AsyncQueue.iterate treats a shifted undefined value as
"empty" which breaks generic correctness; change the check to test buffer length
first (e.g., if (this.buffer.length > 0) { const next = this.buffer.shift()!;
yield next; continue; }) so actual undefined items are yielded; keep the rest of
the loop (this.closed, waiters promise handling and result.done handling)
unchanged and reference AsyncQueue.iterate, this.buffer, this.waiters, and
this.closed when applying the change.
🪄 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: d0502fba-b884-4dfd-92fa-95fdf3b5814e

📥 Commits

Reviewing files that changed from the base of the PR and between 69b2c89 and 5dddc70.

📒 Files selected for processing (15)
  • .claude/docs/workflow-yaml-reference.md
  • migrations/000_combined.sql
  • migrations/001_initial_schema.sql
  • packages/cli/src/commands/setup.ts
  • packages/core/package.json
  • packages/core/src/config/config-loader.ts
  • packages/core/src/db/codebases.ts
  • packages/core/src/db/conversations.ts
  • packages/core/src/handlers/clone.ts
  • packages/core/src/orchestrator/orchestrator-agent.ts
  • packages/providers/src/community/pi/event-bridge.ts
  • packages/server/package.json
  • packages/server/src/routes/api.ts
  • packages/workflows/package.json
  • packages/workflows/src/loader.ts

Comment thread .claude/docs/workflow-yaml-reference.md
Comment thread packages/cli/src/commands/setup.ts
Comment thread packages/core/src/db/conversations.ts
Comment thread packages/core/src/handlers/clone.ts
Comment thread packages/server/src/routes/api.ts
@worldomkar

Copy link
Copy Markdown
Author

@coleam00 tagging for "pi-coding-agent" support.

Omkar added 5 commits May 4, 2026 11:56
Build an enabled providers list from detected SDKs and
prioritize DEFAULT_AI_ASSISTANT against it. Fall back
to the first available provider before checking registry
order. This prevents selecting a disabled AI SDK.
Add unit tests for the new error behavior. Change log level to error.
Add resolveAssistantType to validate provider IDs and legacy
folder names before passing to createCodebase. This prevents invalid
assistant types from reaching downstream logic and ensures safe
fallbacks when environment variable configuration or folder
detection fails.
Add retry logic for conversation auto-creation races.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (1)
packages/core/src/db/conversations.ts (1)

76-112: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

null from DB bypasses the loadConfig() fallback

assistantType is initialized to undefined, but lines 86 and 104 assign it directly from query results, where the DB column can be NULL after the migration that changed the ai_assistant_type default to NULL. Strict === undefined on line 109 leaves assistantType as null and inserts null into the new conversation, bypassing the intended config.assistant fallback.

🐛 Proposed fix
-  if (assistantType === undefined) {
+  // Use `== null` (loose) to catch both `undefined` and DB-returned `null`.
+  if (assistantType == null) {
     const config = await loadConfig();
     assistantType = config.assistant;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/db/conversations.ts` around lines 76 - 112, The bug is that
DB NULL values for ai_assistant_type get assigned to assistantType and the final
fallback only checks for undefined, so NULL bypasses loadConfig(); fix by
treating null the same as undefined—either coerce DB assignments to undefined
(e.g. when setting assistantType from parent.rows[0].ai_assistant_type and
codebase.rows[0].ai_assistant_type) or change the fallback check to use a
nullish check (e.g. if (assistantType == null) or if (assistantType === null ||
assistantType === undefined)) so loadConfig().assistant is used when the DB
value is NULL; update references in this block around assistantType,
parent.rows[0].ai_assistant_type, codebase.rows[0].ai_assistant_type and the
final loadConfig() fallback.
♻️ Duplicate comments (1)
packages/cli/src/commands/setup.ts (1)

878-899: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Two residual gaps in the default-assistant override branches still exclude pi — partly addressing the concern raised in the previous review.

Bug 1 — Line 897 forces 'codex' when hasPi && hasCodex && !hasClaude:
At line 869–876, defaultAssistant is correctly computed from enabledProviders (which includes 'pi'). For example, if process.env.DEFAULT_AI_ASSISTANT === 'pi' and the user selected codex + pi, the computed value is 'pi'. The unconditional else if (hasCodex && !hasClaude) override on line 897 then clobbers that to 'codex'.

Bug 2 — Lines 879–884 filter to p.builtIn only, excluding pi when all three are selected:
getRegisteredProviders().filter(p => p.builtIn) is evaluated only when hasClaude && hasCodex. If hasPi is also true the select prompt never surfaces 'pi' as a choice.

🐛 Proposed fix — generalise both branches to `enabledProviders`
-  if (hasClaude && hasCodex) {
+  if (enabledProviders.length > 1) {
     const providerChoices = getRegisteredProviders()
-      .filter(p => p.builtIn)
+      .filter(p => enabledProviders.includes(p.id))
       .map(p => ({
         value: p.id,
         label: p.id === 'claude' ? `${p.displayName} (Recommended)` : p.displayName,
       }));

     const defaultChoice = await select({
       message: 'Which should be the default AI assistant?',
       options: providerChoices,
     });

     if (isCancel(defaultChoice)) {
       cancel('Setup cancelled.');
       process.exit(0);
     }

     defaultAssistant = defaultChoice;
-  } else if (hasCodex && !hasClaude) {
-    defaultAssistant = 'codex';
   }

With enabledProviders.length > 1, the single-provider cases (any single selection) skip the prompt entirely and keep defaultAssistant from the already-correct initial computation at line 869. All multi-provider combinations — including hasPi && hasCodex && !hasClaude and hasPi && hasClaude && hasCodex — now show a selection drawn from exactly the enabled set.

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

In `@packages/cli/src/commands/setup.ts` around lines 878 - 899, The
default-assistant logic incorrectly excludes 'pi' by using
getRegisteredProviders().filter(p => p.builtIn) for the multi-provider prompt
and by unconditionally setting defaultAssistant = 'codex' in the hasCodex &&
!hasClaude branch; change both to use the already-computed enabledProviders
list: build providerChoices from enabledProviders (map id/displayName) when
prompting via select(), and remove or replace the unconditional assignment of
'codex' so that defaultAssistant preserves the initial computed value from
enabledProviders (or is set from the user's select() result, guarded by
isCancel()/cancel()). Ensure you reference enabledProviders, defaultAssistant,
getRegisteredProviders(), select(), isCancel(), and cancel() while making these
edits.
🧹 Nitpick comments (2)
packages/core/src/db/conversations.test.ts (1)

72-92: ⚡ Quick win

Tests depend on live loadConfig() — non-deterministic against real config files or future built-in providers

getOrCreateConversation now calls loadConfig() when assistantType is still undefined, but this test file doesn't mock ../config/config-loader. The hardcoded 'claude' expectation in this test (line 90) and falls back to claude when codebase not found (line 167) will silently break if:

  • registerBuiltinProviders() inside loadConfig() registers any built-in provider whose ID isn't 'claude' (the first built-in takes priority per getDefaults()), or
  • a ~/.archon/config.yaml with assistant: pi (or similar) is present in the test runner's home.

Since loadConfig is importable and only config.assistant is consumed in this path, a module-level mock is straightforward to add. As per coding guidelines, "keep tests deterministic — no flaky timing or network dependence without guardrails."

♻️ Suggested fix — add a module-level mock before the imports
+mock.module('../config/config-loader', () => ({
+  loadConfig: async () => ({ assistant: 'claude' }),
+}));
+
 import {
   getOrCreateConversation,

Note: mock.module() is process-global in Bun and must appear before the module under test is imported; the partial return value is sufficient because only config.assistant is read by getOrCreateConversation.

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

In `@packages/core/src/db/conversations.test.ts` around lines 72 - 92, Tests call
getOrCreateConversation which will call loadConfig() when assistantType is
undefined; the test currently doesn't mock ../config/config-loader so it can
pick up real config and change the expected 'claude' default. Add a module-level
mock for '../config/config-loader' before importing the module under test (use
mock.module() / jest.mock at top of the test file) that returns an object with
config.assistant set to 'claude' (or the expected default), so
getOrCreateConversation and the assertions that expect 'claude' remain
deterministic.
packages/server/src/routes/api.ts (1)

14-14: Consider using named import for js-yaml for consistency

The current default import import jsYaml from 'js-yaml' is valid because esModuleInterop is enabled in tsconfig. However, named imports are more explicit and eliminate any future concerns if esModuleInterop is ever adjusted. This is an optional refactoring:

Suggested improvement
-import jsYaml from 'js-yaml';
+import { dump as jsYamlDump } from 'js-yaml';

Then update the call sites:

-      yamlContent = jsYaml.dump(definition);
+      yamlContent = jsYamlDump(definition);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/routes/api.ts` at line 14, Change the default import of
the YAML library to a named import to avoid relying on esModuleInterop: replace
the default import of "js-yaml" with a named import (e.g., import { load, dump }
from 'js-yaml') and update all call sites that currently reference
jsYaml.load/jsYaml.dump to use the corresponding named functions (or re-export
them if you prefer). Locate the import in the top of the file where "import
jsYaml from 'js-yaml'" appears and adjust usages throughout this module (calls
to jsYaml.load, jsYaml.dump, etc.) to the chosen named identifiers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/core/src/db/conversations.ts`:
- Around line 76-112: The bug is that DB NULL values for ai_assistant_type get
assigned to assistantType and the final fallback only checks for undefined, so
NULL bypasses loadConfig(); fix by treating null the same as undefined—either
coerce DB assignments to undefined (e.g. when setting assistantType from
parent.rows[0].ai_assistant_type and codebase.rows[0].ai_assistant_type) or
change the fallback check to use a nullish check (e.g. if (assistantType ==
null) or if (assistantType === null || assistantType === undefined)) so
loadConfig().assistant is used when the DB value is NULL; update references in
this block around assistantType, parent.rows[0].ai_assistant_type,
codebase.rows[0].ai_assistant_type and the final loadConfig() fallback.

---

Duplicate comments:
In `@packages/cli/src/commands/setup.ts`:
- Around line 878-899: The default-assistant logic incorrectly excludes 'pi' by
using getRegisteredProviders().filter(p => p.builtIn) for the multi-provider
prompt and by unconditionally setting defaultAssistant = 'codex' in the hasCodex
&& !hasClaude branch; change both to use the already-computed enabledProviders
list: build providerChoices from enabledProviders (map id/displayName) when
prompting via select(), and remove or replace the unconditional assignment of
'codex' so that defaultAssistant preserves the initial computed value from
enabledProviders (or is set from the user's select() result, guarded by
isCancel()/cancel()). Ensure you reference enabledProviders, defaultAssistant,
getRegisteredProviders(), select(), isCancel(), and cancel() while making these
edits.

---

Nitpick comments:
In `@packages/core/src/db/conversations.test.ts`:
- Around line 72-92: Tests call getOrCreateConversation which will call
loadConfig() when assistantType is undefined; the test currently doesn't mock
../config/config-loader so it can pick up real config and change the expected
'claude' default. Add a module-level mock for '../config/config-loader' before
importing the module under test (use mock.module() / jest.mock at top of the
test file) that returns an object with config.assistant set to 'claude' (or the
expected default), so getOrCreateConversation and the assertions that expect
'claude' remain deterministic.

In `@packages/server/src/routes/api.ts`:
- Line 14: Change the default import of the YAML library to a named import to
avoid relying on esModuleInterop: replace the default import of "js-yaml" with a
named import (e.g., import { load, dump } from 'js-yaml') and update all call
sites that currently reference jsYaml.load/jsYaml.dump to use the corresponding
named functions (or re-export them if you prefer). Locate the import in the top
of the file where "import jsYaml from 'js-yaml'" appears and adjust usages
throughout this module (calls to jsYaml.load, jsYaml.dump, etc.) to the chosen
named identifiers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9e919f84-2d8d-4f8b-a3b8-d1f925de0255

📥 Commits

Reviewing files that changed from the base of the PR and between 5dddc70 and b027e46.

📒 Files selected for processing (8)
  • .claude/docs/workflow-yaml-reference.md
  • packages/cli/src/commands/setup.ts
  • packages/core/src/db/conversations.test.ts
  • packages/core/src/db/conversations.ts
  • packages/core/src/handlers/clone.ts
  • packages/providers/src/index.ts
  • packages/providers/src/registry.ts
  • packages/server/src/routes/api.ts
✅ Files skipped from review due to trivial changes (1)
  • .claude/docs/workflow-yaml-reference.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/handlers/clone.ts

Omkar added 3 commits May 4, 2026 13:39
Check buffer length before shifting to avoid undefined returns.
This allows a safe type assertion and removes redundant checks.
@worldomkar

Copy link
Copy Markdown
Author

Just realised, this only fixes webchat interaction between archon and pi. I'm trying to run a simple workflow and it is getting confused about session ids.

Still this is a working starting point - at least chat with "pi" is possible in archon's webchat. @coleam00

@Wirasm

Wirasm commented May 4, 2026

Copy link
Copy Markdown
Collaborator

@worldomkar related to #1541 — Pi provider chunking and invocation issues.

@Wirasm

Wirasm commented May 4, 2026

Copy link
Copy Markdown
Collaborator

Hi @worldomkar — thanks for opening this PR.

This repository uses a PR template at .github/pull_request_template.md with several required
sections. A few of them appear to be empty or placeholder here:

  • UX Journey
  • Architecture Diagram
  • Label Snapshot
  • Change Metadata
  • Validation Evidence
  • Security Impact
  • Compatibility / Migration
  • Human Verification
  • Side Effects / Blast Radius
  • Rollback Plan
  • Risks and Mitigations

Could you fill those out (even briefly)? The template
helps reviewers understand scope, risk, and rollback — it
speeds up review significantly.

If a section genuinely doesn't apply, just write "N/A" in
it rather than leaving it blank.

@worldomkar

worldomkar commented May 4, 2026

Copy link
Copy Markdown
Author

Thanks @Wirasm for pointing that out. I believe red tapes like these must be delegated to coderabbit (or other ai) - as it is more of roadblock than constructive synergic development. I do not have that much time to follow the red tape so mentioned what it does. Interestingly PR was reviewed by coderabbit, and also it provided me "agent prompts" to addreass those issues. So I gave it a try in my local "pi" coding agent without working "archon", and pushed. I have already spent enough time on this to make it work - I have my own development to do that would speed up significantly if archon + pi could just work. I'm working on other bottlenecks with pi.

Hope you understand and take this as process improvement note.

PS:
This PR only fixes "pi" provider selection from DEFAULT_AI_ASSISTANT from .env - it doesn't fix or relates to #1541.

I thought archon just failed to pickup default provider - so I tried to fix; but seems like archon+pi implementation is in its early stages - not useable as-is even though it was mentinoed as priority in youtube video. (I refer to dev branch).

Its really promising to see "archon" + "pi" working. the database record fix was actually reported by archon to have been set on database level so irrespective of my environment config provider was being picked up from within database at project level, then I asked pi to fix that. :)

Thanks.

@worldomkar

Copy link
Copy Markdown
Author

Just checking:

@coderabbitai can you provide answers to questions @Wirasm asked?

#1553 (comment)

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR template
cat .github/pull_request_template.md 2>/dev/null || echo "NOT FOUND"

# Fetch PR body via GitHub CLI
gh pr view 1553 --repo coleam00/Archon --json body -q '.body'

Length of output: 4637


@worldomkar — here are answers to each of the sections @Wirasm requested, based on the code changes in this PR. You can copy these into the PR description.


UX Journey

Before

User sets DEFAULT_AI_ASSISTANT=pi in .env
         │
         ▼
Archon resolves assistant ──▶ hardcoded fallback 'claude'
         │                     (pi is never selected)
         ▼
Webchat / workflow runs with Claude regardless of config

After

User sets DEFAULT_AI_ASSISTANT=pi in .env
         │
         ▼
[Archon reads DEFAULT_AI_ASSISTANT] ──▶ resolveAssistantType('pi')
         │
         ▼
DB stores assistantType='pi' (no longer defaults to 'claude')
         │
         ▼
Webchat runs with pi model (e.g. macbook/mlx-community/Qwen3-35B)

Architecture Diagram

Modules touched:

From To Status Notes
packages/cli/src/commands/setup.ts providers/registry modified Calls resolveAssistantType; pi added to assistant multiselect
packages/core/src/config/config-loader.ts js-yaml new Replaces Bun.YAML with js-yaml for YAML parse/dump
packages/core/src/db/codebases.ts loadConfig() modified Uses config.assistant instead of hardcoded 'claude'
packages/core/src/db/conversations.ts loadConfig() modified Fallback chain: parent → codebase → config.assistant (no longer process.env directly)
packages/core/src/handlers/clone.ts resolveAssistantType modified Env var resolved through registry; fallbacks added
packages/core/src/orchestrator/orchestrator-agent.ts SessionNotFoundError modified Ignores SessionNotFoundError on session persist (non-critical)
packages/providers/src/registry.ts (new export) new resolveAssistantType(id) added and re-exported
packages/server/src/routes/api.ts js-yaml / conversationDb modified YAML via js-yaml; auto-create conversation on missing web session
packages/workflows/src/loader.ts js-yaml modified YAML parse via js-yaml instead of Bun.YAML.parse
migrations/000_combined.sql + 001_initial_schema.sql DB schema modified ai_assistant_type default changed from 'claude'NULL

Label Snapshot

  • Risk: risk: medium — DB schema default change and YAML library swap affect all deployments
  • Size: size: M — 13 files changed, cross-cutting but focused
  • Scope: core, config, adapters, cli, server, workflows, dependencies
  • Module: core:config, core:db, adapters:pi, providers:registry

Change Metadata

  • Change type: bug + feature (fixes hardcoded provider; adds pi as selectable assistant)
  • Primary scope: multi

Validation Evidence

The author confirmed webchat interaction between Archon and pi works. However:

  • Full CI validation (bun run type-check, bun run lint, bun run test) evidence has not been provided in the PR.
  • Workflow execution with pi still has session ID issues (acknowledged by author — out of scope for this PR).

Security Impact

  • New permissions/capabilities? No
  • New external network calls? No (pi communicates via local model endpoint)
  • Secrets/tokens handling changed? No
  • File system access scope changed? No

Compatibility / Migration

  • Backward compatible? Mostly Yes, with one caveat: the ai_assistant_type DB column default changes from 'claude' to NULL. Existing rows are unaffected, but new inserts on existing deployments that previously relied on the 'claude' DB default will now get NULL if loadConfig() is not configured.
  • Config/env changes? YesDEFAULT_AI_ASSISTANT in .env is now the source of truth for the default assistant.
  • Database migration needed? Yesmigrations/000_combined.sql and migrations/001_initial_schema.sql both change the column default. Existing deployments should run the updated migration.
  • Upgrade steps: Ensure DEFAULT_AI_ASSISTANT=<your-provider> is set in your .env, then re-run migrations.

Human Verification

  • Verified scenarios: Webchat with pi provider works end-to-end in Archon's web UI after setting DEFAULT_AI_ASSISTANT=pi.
  • Edge cases checked: DB record was previously locking in claude at the project level; fix confirmed to resolve that.
  • What was not verified: Workflow execution with pi (session ID resolution still broken — separate issue from #1541).

Side Effects / Blast Radius

  • Affected subsystems: Config loading, DB conversations/codebases, clone handler, orchestrator session persistence, setup CLI wizard, workflow YAML loader, server API auto-conversation creation.
  • Potential unintended effects:
    • Deployments without DEFAULT_AI_ASSISTANT set will get NULL as assistant type from DB default — fallback logic in loadConfig() must handle this gracefully.
    • Replacing Bun.YAML with js-yaml across all YAML paths — any YAML edge cases handled differently by the two libraries could surface as regressions.
  • Guardrails: The resolveAssistantType function throws with a list of valid providers on invalid input, surfacing misconfiguration early.

Rollback Plan

  • Fast rollback: Revert this PR branch; re-run prior migrations to restore DEFAULT 'claude' on ai_assistant_type columns.
  • Feature flags / config toggles: None — controlled entirely via DEFAULT_AI_ASSISTANT env var.
  • Observable failure symptoms: Assistant type resolves to NULL or wrong provider; webchat/workflow messages fail to dispatch; resolveAssistantType errors appear in logs with "valid providers" message.

Risks and Mitigations

Risk Mitigation
DB default change ('claude'NULL) breaks existing deployments that rely on DB-level default loadConfig() fallback chain handles NULL; document migration requirement
js-yaml replacing Bun.YAML may produce different output for edge-case YAML js-yaml is the standard library; Bun's YAML was an unstable internal API. Run bun run test to catch regressions
Workflow session ID issues with pi (acknowledged as out of scope) Tracked separately; related to #1541
resolveAssistantType throws on unrecognized provider IDs, potentially breaking existing setups with non-standard provider names Catch blocks in clone.ts and setup.ts fall back to 'claude' or the first registered provider

@worldomkar

worldomkar commented May 4, 2026

Copy link
Copy Markdown
Author

@Wirasm seems simple comment worked 👍; just needs some automation.

@worldomkar

worldomkar commented May 4, 2026

Copy link
Copy Markdown
Author

I don't know why I attempted to fix @CodeRabbit 's review comments, and asked pi to address them - they actually broke this PR. the last known working commit is: 5dddc70 - which shall be taken as a base. I will leave it for someone to revert this PR I have reverted this PR to that working commit. (@CodeRabbit can you see what broke this between that commit and latest following your suggestion?)

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Show commits between the known-working commit and HEAD
git log --oneline 5dddc70cbb704b7b5aee413c4db9ee8ec793f52a..HEAD

echo "---DIFF STAT---"
git diff --stat 5dddc70cbb704b7b5aee413c4db9ee8ec793f52a HEAD

echo "---FULL DIFF---"
git diff 5dddc70cbb704b7b5aee413c4db9ee8ec793f52a HEAD

Length of output: 14772


@worldomkar — here's a breakdown of the 8 commits that landed after 5dddc70 and what each one likely broke.


Commits added after the working base

Commit Message
7791592 update workflow-yaml-reference.md (doc only, safe)
0cac232 Refactor default assistant selection logic
b6dbf4e Throw error when updating title for missing conversation
92a09f2 Validate and normalize assistant type resolution
b027e46 Return 500 responses on conversation lookup failures
c43e641 Simplify AsyncQueue iteration logic (likely safe)
a6cdff8 Convert caught unknown errors to Error objects (likely safe)
bf034f0 Fix Config Fallback

The three most likely culprits

1. b6dbf4eupdateConversationTitle now throws instead of being silent

Before (5dddc70): The JSDoc said "Fire-and-forget safe — does not throw when the conversation does not exist." It just logged a warning.

After: It throws ConversationNotFoundError. Any caller (e.g. the title-generator) that wasn't wrapping it in try/catch will now crash with an unhandled error.

- log.warn({ conversationId: id }, 'update_conversation_title_not_found');
+ log.error({ conversationId: id }, 'update_conversation_title_not_found');
+ throw new ConversationNotFoundError(id);

2. 92a09f2resolveAssistantType throws on unrecognized providers

The new resolveAssistantType function in registry.ts throws if the provider isn't registered. In clone.ts, it's called with process.env.DEFAULT_AI_ASSISTANT before built-in providers are necessarily registered. If the registry is empty at that point, even 'claude' and 'codex' will throw (falling back to 'claude' via the catch, masking the real provider).

// This throws if providers not yet registered — and catch silently falls back to 'claude'
defaultProvider = resolveAssistantType(process.env.DEFAULT_AI_ASSISTANT ?? fallback);

3. b027e46 — Conversation lookup failure now returns 500 instead of continuing

Before: A DB error on findConversationByPlatformId was logged and execution continued. After: It immediately returns a 500 response. Any transient lookup error that was previously swallowed will now surface as a hard failure to the client.


Recommended path to a clean revert

To revert to 5dddc70 while keeping the doc-only commit (7791592), you can run:

git revert b6dbf4e 92a09f2 b027e46 0cac232 bf034f0 --no-commit
git commit -m "Revert breaking post-5dddc70 commits, keep working pi provider base"

Or to simply hard-reset the branch tip back to the working commit (losing all 8 commits):

git reset --hard 5dddc70cbb704b7b5aee413c4db9ee8ec793f52a
git push --force-with-lease

The changes in a6cdff8 (error normalization in event-bridge.ts) and c43e641 (AsyncQueue length guard) look safe and could be cherry-picked back on top of 5dddc70 if desired, since they were targeted pi bug fixes.

@worldomkar

Copy link
Copy Markdown
Author

@CodeRabbit, can you revert it to that commit? 🧐

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@worldomkar — Unfortunately, I'm not able to push commits or force-reset branches directly — that would require write access to your fork and falls outside what I can safely do as a code review assistant.

You'll need to run the reset yourself locally. Here are the exact steps:

Option A — Hard reset to the working commit (simplest):

git checkout bugfix/fix-pi-provider
git fetch origin
git reset --hard 5dddc70cbb704b7b5aee413c4db9ee8ec793f52a
git push --force-with-lease origin bugfix/fix-pi-provider

Option B — Keep the two safe fixes (a6cdff8 + c43e641) on top of the working base:

git checkout bugfix/fix-pi-provider
git fetch origin
git reset --hard 5dddc70cbb704b7b5aee413c4db9ee8ec793f52a
# Cherry-pick the safe AsyncQueue and error-normalization fixes
git cherry-pick c43e641
git cherry-pick a6cdff8
git push --force-with-lease origin bugfix/fix-pi-provider

Option C — Selective revert (preserves history, safer for shared branches):

git revert bf034f0 0cac232 b027e46 92a09f2 b6dbf4e --no-commit
git commit -m "Revert breaking post-5dddc70 commits, restore working pi provider base"
git push origin bugfix/fix-pi-provider

⚠️ Use --force-with-lease rather than --force — it will abort if someone else has pushed since you last fetched, preventing accidental overwrites.

Once you've pushed, the PR will automatically update and I can do a fresh review of the restored state. Let me know if you'd like that!

@leex279

leex279 commented May 4, 2026

Copy link
Copy Markdown
Collaborator

🔗 Related issue: This PR may be related to issue #1171 ("Settings default assistant save is masked by DEFAULT_AI_ASSISTANT in Docker"). Both involve how the AI assistant default selection interacts with environment variable configuration.

@worldomkar

worldomkar commented May 5, 2026

Copy link
Copy Markdown
Author

Tip

For best results, initiate chat on the files or code changes.
@worldomkar — Unfortunately, I'm not able to push commits or force-reset branches directly — that would require write access to your fork and falls outside what I can safely do as a code review assistant.

You'll need to run the reset yourself locally. Here are the exact steps:

Option A — Hard reset to the working commit (simplest):

git checkout bugfix/fix-pi-provider
git fetch origin
git reset --hard 5dddc70cbb704b7b5aee413c4db9ee8ec793f52a
git push --force-with-lease origin bugfix/fix-pi-provider

Option B — Keep the two safe fixes (a6cdff8 + c43e641) on top of the working base:

git checkout bugfix/fix-pi-provider
git fetch origin
git reset --hard 5dddc70cbb704b7b5aee413c4db9ee8ec793f52a
# Cherry-pick the safe AsyncQueue and error-normalization fixes
git cherry-pick c43e641
git cherry-pick a6cdff8
git push --force-with-lease origin bugfix/fix-pi-provider

Option C — Selective revert (preserves history, safer for shared branches):

git revert bf034f0 0cac232 b027e46 92a09f2 b6dbf4e --no-commit
git commit -m "Revert breaking post-5dddc70 commits, restore working pi provider base"
git push origin bugfix/fix-pi-provider

⚠️ Use --force-with-lease rather than --force — it will abort if someone else has pushed since you last fetched, preventing accidental overwrites.

Once you've pushed, the PR will automatically update and I can do a fresh review of the restored state. Let me know if you'd like that!

okay I have done a git revert already, but I will push in some time I have pushed reverted working PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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/handlers/clone.ts (1)

49-52: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

DEFAULT_AI_ASSISTANT is still effectively deprioritized and unvalidated (regression).

Line 49-Line 52 still prefers the first built-in provider over process.env.DEFAULT_AI_ASSISTANT, so user-configured pi won’t be selected in common cases. Also, when env is used, it can flow into persistence without provider validation.

Suggested fix
-  const { getRegisteredProviders } = await import('@archon/providers');
-  const defaultProvider =
-    getRegisteredProviders().find(p => p.builtIn)?.id ??
-    process.env.DEFAULT_AI_ASSISTANT ??
-    'claude';
+  const { getRegisteredProviders, isRegisteredProvider } = await import('@archon/providers');
+  const envAssistant = process.env.DEFAULT_AI_ASSISTANT?.trim();
+
+  const defaultProvider = (() => {
+    if (envAssistant) {
+      if (!isRegisteredProvider(envAssistant)) {
+        const available = getRegisteredProviders().map(p => p.id).join(', ');
+        throw new Error(
+          `DEFAULT_AI_ASSISTANT='${envAssistant}' is not registered. Available providers: ${available}`
+        );
+      }
+      return envAssistant;
+    }
+
+    // Intentional fallback order when env is unset.
+    return getRegisteredProviders().find(p => p.builtIn)?.id ?? 'claude';
+  })();

As per coding guidelines, "Prefer throwing early with a clear error for unsupported or unsafe states; never silently swallow errors or broaden permissions; document intentional fallback behavior with a comment."

🤖 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.ts` around lines 49 - 52, The current
defaultProvider selection prefers a built-in provider over
process.env.DEFAULT_AI_ASSISTANT and doesn't validate the env value; update the
logic in the defaultProvider initialization (and/or surrounding code that uses
getRegisteredProviders()) to first check process.env.DEFAULT_AI_ASSISTANT, then
validate that its value matches an id returned by getRegisteredProviders(), use
that validated provider id if present, otherwise fall back to the first built-in
or a hardcoded fallback ('claude'); if the env value is set but not recognized,
throw a clear error rather than silently using a different provider so
persistence never receives an unvalidated provider id.
packages/server/src/routes/api.ts (1)

1411-1428: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Conversation bootstrap errors are still swallowed before dispatch.

The past review comment on this block is marked as addressed, but the current code still catches both the lookup and getOrCreateConversation() failures, logs them, and falls through. When conv ends up null, the handler skips persistence at line 1431 and still calls dispatchToOrchestrator(conversationId, …) at line 1467 — accepting work with no DB linkage, so history, titles, and assistant-message persistence are silently lost.

Either bubble the error (return 500) when getOrCreateConversation fails, or retry findConversationByPlatformId once to handle a racing insert and only 500 if that retry still has no row. As per coding guidelines, "Prefer throwing early with a clear error for unsupported or unsafe states; never silently swallow errors or broaden permissions; document intentional fallback behavior with a comment".

🛡️ Suggested fix
     let conv: Awaited<ReturnType<typeof conversationDb.findConversationByPlatformId>> = null;
     try {
       conv = await conversationDb.findConversationByPlatformId(conversationId);
     } catch (e: unknown) {
       getLog().error({ err: e, conversationId }, 'conversation_lookup_failed');
+      return apiError(c, 500, 'Failed to look up conversation');
     }
-    // Auto-create conversation if not found (web chat creates ID but never persists it)
-    if (!conv) {
-      try {
-        conv = await conversationDb.getOrCreateConversation('web', conversationId);
-      } catch (e: unknown) {
-        const err = e as Error;
-        getLog().error({ err, conversationId }, 'conversation_auto_create_failed');
-      }
-    }
+    // Auto-create conversation if not found (web chat creates ID but never persists it)
+    if (!conv) {
+      try {
+        conv = await conversationDb.getOrCreateConversation('web', conversationId);
+      } catch (e: unknown) {
+        // Handle racing insert: re-check before failing.
+        try {
+          conv = await conversationDb.findConversationByPlatformId(conversationId);
+        } catch {
+          conv = null;
+        }
+        if (!conv) {
+          getLog().error({ err: e as Error, conversationId }, 'conversation_auto_create_failed');
+          return apiError(c, 500, 'Failed to persist conversation');
+        }
+      }
+    }
🤖 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/server/src/routes/api.ts` around lines 1411 - 1428, The handler
currently swallows conversation bootstrap errors and proceeds to call
dispatchToOrchestrator with conv==null; change this so that if
conversationDb.getOrCreateConversation throws or conv is null after the first
lookup, retry conversationDb.findConversationByPlatformId once (to handle a
racing insert) and if that retry still returns null, log the error with context
and throw/return a 500 (do not fall through to dispatchToOrchestrator); update
the block around conversationDb.findConversationByPlatformId and
conversationDb.getOrCreateConversation to implement the retry + clear error
throw, and ensure logs include the original error and conversationId for
debugging.
🧹 Nitpick comments (4)
packages/core/src/db/conversations.ts (2)

248-248: ⚡ Quick win

Use the cached getLog() helper instead of re-creating the logger.

Lines 12–15 define getLog() specifically so test mocks can intercept createLogger. Calling createLogger('db.conversations') directly here defeats that pattern (and the rest of the file consistently uses getLog()).

♻️ Proposed change
 export async function updateConversationTitle(id: string, title: string): Promise<void> {
   const dialect = getDialect();
-  const log = createLogger('db.conversations');
   const result = await pool.query(
     `UPDATE remote_agent_conversations SET title = $1, updated_at = ${dialect.now()} WHERE id = $2`,
     [title, id]
   );
   if (result.rowCount === 0) {
-    log.warn({ conversationId: id }, 'update_conversation_title_not_found');
+    getLog().warn({ conversationId: id }, 'db.conversation_title_update_not_found');
   }
 }

The renamed event (db.conversation_title_update_not_found) also matches the {domain}.{action}_{state} convention used by the sibling db.conversation_update_not_found event on line 163. As per coding guidelines, "Use Pino logger factory with event naming format {domain}.{action}_{state}".

🤖 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/db/conversations.ts` at line 248, Replace the direct call
to createLogger('db.conversations') with the cached logger helper getLog() so
tests can mock the logger consistently; also rename the event string where the
title-update-not-found is emitted to "db.conversation_title_update_not_found" to
match the existing naming convention used by "db.conversation_update_not_found"
and the {domain}.{action}_{state} format. Ensure you update the symbol usage
where createLogger was used to instead call getLog() and replace the old event
name with the new one.

86-112: ⚡ Quick win

Add validation for assistantType sourced from database rows.

When assistantType is set from parent.rows[0].ai_assistant_type or codebase.rows[0].ai_assistant_type, it bypasses provider registration validation. The value only reaches isRegisteredProvider() indirectly if it remains undefined and falls back to loadConfig().assistant. A stale database row referencing a removed provider will propagate unchecked. Validate with isRegisteredProvider() and fallback to config.assistant when the stored value is unknown, matching the pattern used in config-loader.ts.

🤖 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/db/conversations.ts` around lines 86 - 112, Validate
assistantType after pulling from parent.rows[0].ai_assistant_type or
codebase.rows[0].ai_assistant_type by calling
isRegisteredProvider(assistantType); if it returns false, load the merged config
via loadConfig() and set assistantType = config.assistant (fallback), ensuring
the database-derived value does not bypass provider registration checks; update
the logic around assistantType assignment in the block that reads from parent
and codebase so both sources are validated and fallback to
loadConfig().assistant when unregistered.
packages/server/src/routes/api.ts (1)

2210-2210: ⚡ Quick win

Pin jsYaml.dump options for deterministic YAML serialization.

Both calls at lines 2210 and 2340 use jsYaml.dump(definition) with defaults that can introduce unwanted mutations:

  • lineWidth: 80 wraps long strings, potentially altering workflow prompts/comments persisted to disk
  • noRefs: false emits YAML anchors/aliases for duplicate objects, which some downstream parsers handle inconsistently

Consider passing explicit options { lineWidth: -1, noRefs: true } and reusing a single constant across both serialization sites.

🤖 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/server/src/routes/api.ts` at line 2210, The YAML serialization calls
using jsYaml.dump(definition) are non-deterministic; update both call sites (the
jsYaml.dump invocation near where yamlContent is set and the second call around
line 2340) to pass explicit options { lineWidth: -1, noRefs: true } and factor
these options into a shared constant (e.g., YAML_DUMP_OPTIONS) so both places
reuse the same settings to prevent line-wrapping and YAML anchors/aliases.
packages/providers/src/community/pi/event-bridge.ts (1)

426-432: 💤 Low value

Stale line-number references inside the finally comment.

The inline cross-references are off: queue.close() is at line 413 (comment says "line 392") and the .then() handlers are at line 358 (comment says "line 344"). This won't affect runtime but will mislead future readers following the breadcrumbs.

📝 Proposed update
-    // Don't await promptPromise. The queue is closed above (line 392), and the
-    // .then() handlers attached at construction (line 344) only push to that
+    // Don't await promptPromise. The queue is closed above (queue.close()), and the
+    // .then() handlers attached at construction (session.prompt().then(...)) only push to that
🤖 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/providers/src/community/pi/event-bridge.ts` around lines 426 - 432,
Update the stale inline comment in the finally block by removing or correcting
the hard-coded line numbers: reference the symbols instead (queue.close(), the
.then() handlers attached when promptPromise is created, session.prompt(),
dispose(), and generator.return()) so readers can find the relevant code without
depending on line numbers; either correct the line numbers to their current
locations or, preferably, replace them with these symbolic references and a
brief note explaining why we must not await promptPromise.
🤖 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/handlers/clone.ts`:
- Around line 49-52: The current defaultProvider selection prefers a built-in
provider over process.env.DEFAULT_AI_ASSISTANT and doesn't validate the env
value; update the logic in the defaultProvider initialization (and/or
surrounding code that uses getRegisteredProviders()) to first check
process.env.DEFAULT_AI_ASSISTANT, then validate that its value matches an id
returned by getRegisteredProviders(), use that validated provider id if present,
otherwise fall back to the first built-in or a hardcoded fallback ('claude'); if
the env value is set but not recognized, throw a clear error rather than
silently using a different provider so persistence never receives an unvalidated
provider id.

In `@packages/server/src/routes/api.ts`:
- Around line 1411-1428: The handler currently swallows conversation bootstrap
errors and proceeds to call dispatchToOrchestrator with conv==null; change this
so that if conversationDb.getOrCreateConversation throws or conv is null after
the first lookup, retry conversationDb.findConversationByPlatformId once (to
handle a racing insert) and if that retry still returns null, log the error with
context and throw/return a 500 (do not fall through to dispatchToOrchestrator);
update the block around conversationDb.findConversationByPlatformId and
conversationDb.getOrCreateConversation to implement the retry + clear error
throw, and ensure logs include the original error and conversationId for
debugging.

---

Nitpick comments:
In `@packages/core/src/db/conversations.ts`:
- Line 248: Replace the direct call to createLogger('db.conversations') with the
cached logger helper getLog() so tests can mock the logger consistently; also
rename the event string where the title-update-not-found is emitted to
"db.conversation_title_update_not_found" to match the existing naming convention
used by "db.conversation_update_not_found" and the {domain}.{action}_{state}
format. Ensure you update the symbol usage where createLogger was used to
instead call getLog() and replace the old event name with the new one.
- Around line 86-112: Validate assistantType after pulling from
parent.rows[0].ai_assistant_type or codebase.rows[0].ai_assistant_type by
calling isRegisteredProvider(assistantType); if it returns false, load the
merged config via loadConfig() and set assistantType = config.assistant
(fallback), ensuring the database-derived value does not bypass provider
registration checks; update the logic around assistantType assignment in the
block that reads from parent and codebase so both sources are validated and
fallback to loadConfig().assistant when unregistered.

In `@packages/providers/src/community/pi/event-bridge.ts`:
- Around line 426-432: Update the stale inline comment in the finally block by
removing or correcting the hard-coded line numbers: reference the symbols
instead (queue.close(), the .then() handlers attached when promptPromise is
created, session.prompt(), dispose(), and generator.return()) so readers can
find the relevant code without depending on line numbers; either correct the
line numbers to their current locations or, preferably, replace them with these
symbolic references and a brief note explaining why we must not await
promptPromise.

In `@packages/server/src/routes/api.ts`:
- Line 2210: The YAML serialization calls using jsYaml.dump(definition) are
non-deterministic; update both call sites (the jsYaml.dump invocation near where
yamlContent is set and the second call around line 2340) to pass explicit
options { lineWidth: -1, noRefs: true } and factor these options into a shared
constant (e.g., YAML_DUMP_OPTIONS) so both places reuse the same settings to
prevent line-wrapping and YAML anchors/aliases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 25b8ae84-f1bc-4f4a-8834-3ef7040a1ec5

📥 Commits

Reviewing files that changed from the base of the PR and between bf034f0 and ba0b919.

📒 Files selected for processing (10)
  • packages/cli/src/commands/setup.ts
  • packages/core/src/db/conversations.ts
  • packages/core/src/handlers/clone.ts
  • packages/core/src/orchestrator/orchestrator-agent.ts
  • packages/providers/src/claude/provider.ts
  • packages/providers/src/community/pi/event-bridge.ts
  • packages/providers/src/community/pi/provider.ts
  • packages/server/package.json
  • packages/server/src/routes/api.ts
  • packages/workflows/src/dag-executor.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/workflows/src/dag-executor.ts
  • packages/providers/src/claude/provider.ts
  • packages/providers/src/community/pi/provider.ts
  • packages/server/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/orchestrator/orchestrator-agent.ts
  • packages/cli/src/commands/setup.ts

@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Show commits between the known-working commit and HEAD
git log --oneline 5dddc70cbb704b7b5aee413c4db9ee8ec793f52a..HEAD

echo "---DIFF STAT---"
git diff --stat 5dddc70cbb704b7b5aee413c4db9ee8ec793f52a HEAD

echo "---FULL DIFF---"
git diff 5dddc70cbb704b7b5aee413c4db9ee8ec793f52a HEAD

Length of output: 287117


@worldomkar thanks for the log. Short answer: the breakages between 5dddc70 and your earlier HEAD came from three commits that changed error/selection semantics — your revert took them out, so you’re back on a good base.

What broke before

  • b6dbf4e: updateConversationTitle started throwing when the row didn’t exist (previously logged and continued) — unhandled in callers → crashes.
  • 92a09f2: resolveAssistantType began throwing for unknown/unregistered IDs — easy to hit before providers are registered; masked real defaults.
  • b027e46: server returned 500 on conversation lookup failures instead of continuing/creating — surfaced as hard failures.

You reverted these (9376e40), so that set of issues is gone.

What the “merge dev” brought back in (generally good/safe, but worth a quick sanity check)

  • Pi runtime stability:
    • Event bridge: error normalization + non-blocking cleanup on dispose (prevents hangs if prompt() never settles).
    • Optional concurrency cap: assistants.pi.maxConcurrent to avoid 429 cascades on parallel runs.
    • Settings baseline: Pi reads ~/.pi/agent/settings.json and /.pi/settings.json as the starting point for every session.
  • Orchestrator: ensures ~/.archon/workspaces exists before provider spawn — fixes a class of “session/workspace” races.
  • Server /api/conversations/{id}/message: will auto-create the conversation row when missing instead of 500.
  • Workflow engine: dot-notation now JSON-stringifies arrays/objects in $node.output.* (safer for Bash and comparisons).
  • CLI: archon doctor to verify Claude binary, gh auth, DB reachability, adapters, etc.

Quick validations I recommend now

  • Confirm Pi as default and basic chat+workflow paths:
    # Ensure default assistant is Pi
    rg -n "defaultAssistant" .archon/config.yaml || true
    # or rely on env
    grep -n '^DEFAULT_AI_ASSISTANT=pi' ~/.archon/.env
    
    # 1) Doctor
    archon doctor
    
    # 2) Webchat with Pi (already OK per you)
    
    # 3) Minimal workflow with Pi
    archon workflow run archon-assist --cwd /path/to/repo --no-worktree "ping"
  • If you run parallel workflows, set a conservative cap while debugging:
    # .archon/config.yaml
    assistants:
      pi:
        maxConcurrent: 2
  • Docker note (only if you’re using it): set PI_CODING_AGENT_DIR=/.archon/pi in .env if you prefer Pi data to live on the persisted ARCHON_DATA volume; otherwise it lives under /home/appuser (now persisted by default).

TL;DR

  • Root cause was those three commits (throwing/strict/500). Your revert removed them.
  • After merging dev, you now have a bunch of Pi and orchestration fixes that should help the “session id confusion” you mentioned. If anything still misbehaves after the quick checks above, tell me the exact command or route that fails and I’ll trace it against the new code paths.

@Wirasm

Wirasm commented May 14, 2026

Copy link
Copy Markdown
Collaborator

Review Summary

Verdict: blocking-issues

This PR adds Pi as a first-class built-in provider and migrates all YAML parsing from Bun.YAML to js-yaml. The migration is consistent and well-scoped. Three behavioral changes lack test coverage (creating regression risk), loadConfig() moved into a hot path where it will cause timeouts under load, and updateConversationTitle now silently loses title updates instead of surfacing errors. Two user-facing doc gaps also need fixing before merge.

Blocking issues

  • packages/core/src/db/conversations.ts:107-113loadConfig() (async disk-read + YAML parse) is now called on every new conversation creation. Under concurrent load this will block the message handler and cause timeouts.

    • Fix: Pre-load config once at process bootstrap and pass it via WorkflowDeps, or cache config.assistant with a TTL.
  • packages/core/src/db/codebases.ts:19-22 — New config.assistant fallback in createCodebase has no test. The existing 'defaults ai_assistant_type to claude' test passes coincidentally because the real loadConfig falls back to 'claude' in test environments — it will silently insert 'pi' if config.assistant is 'pi' with no assertion.

    • Fix: Mock loadConfig to return { assistant: 'pi' }, call createCodebase without ai_assistant_type, assert INSERT receives 'pi'.
  • packages/core/src/db/conversations.ts:244-250updateConversationTitle changed from throwing ConversationNotFoundError to logging a warn and returning silently. This behavioral change has no regression test. The existing 'throws ConversationNotFoundError when conversation not found' test covers updateConversation, not updateConversationTitle.

    • Fix: Call updateConversationTitle with a non-existent ID and assert (1) no error thrown, (2) warn logged with { conversationId }.
  • packages/docs-web/src/content/docs/guides/authoring-workflows.md:202 — The provider field table still lists only 'claude' | 'codex'. pi is a valid registered provider and must be listed (line 689 already correctly shows claude, codex, pi — the table above just isn't in sync).

    • Fix: Update |'codex' to |'codex' | 'pi' and include pi in the "e.g. 'claude', 'codex'" copy.

Suggested fixes

  • packages/core/src/db/conversations.ts:238-246updateConversationTitle silently swallows missing conversations. Callers that don't auto-create will proceed with missing DB records. The title-generator's try-catch is the only thing preventing silent data loss right now.

    • Fix: Add a code-level comment at the warn site: // Safe to swallow — caller auto-creates or handles missing conversations. Document the assumption where the behavior happens, not just in the JSDoc.
  • packages/cli/src/commands/setup.ts:344-351hasPi detection via four new env vars has no tests. hasClaude/hasCodex are fully covered but the new hasPi path is not.

    • Fix: Set PI_CODING_AGENT_DIR=/path/to/pi, call checkExistingConfig(), assert result.hasPi === true.
  • packages/cli/src/commands/setup.ts:787-805 — The !hasClaude && !hasCodex && !hasPi guard and DEFAULT_AI_ASSISTANT fallback in collectAIConfig have no tests. config.pi: boolean in the return shape is also uncovered.

    • Fix: Simulate hasPi = true with no Claude/Codex, verify config.pi === true and defaultAssistant resolves correctly.
  • packages/core/src/handlers/clone.ts:49-52 — New DEFAULT_AI_ASSISTANT fallback in registerRepoAtPath when no built-in provider folder is found has no tests.

    • Fix: Mock getRegisteredProviders() to return [], set DEFAULT_AI_ASSISTANT=pi, call registerRepoAtPath, assert ai_assistant_type = 'pi'.
  • packages/core/src/orchestrator/orchestrator-agent.ts:354-363 — The SessionNotFoundError swallow in tryPersistSessionId has no test, despite three legitimate failure modes being documented.

    • Fix: Mock sessionDb.updateSession to throw SessionNotFoundError, call the orchestrator flow, assert no error propagates.
  • packages/docs-web/src/content/docs/reference/configuration.md (after assistants.codex block) — assistants.pi config block with model and enableExtensions fields is not documented. Users configuring Pi have no reference.

    • Fix: Add a pi config block after the codex entry:
      # Pi (community provider — installed with Archon)
      pi:
        model: google/gemini-2.0-flash  # Format: '<pi-provider-id>/<model-id>'
        enableExtensions: true           # Default: true. Set false to skip extension discovery.

Minor / nice-to-have

  • packages/core/src/orchestrator/orchestrator-agent.ts:354-366SessionNotFoundError is imported on line 23 but the catch block uses a bare identifier. Verify the import resolves correctly, or use if (err instanceof SessionNotFoundError) to make intent explicit.

  • packages/providers/src/community/pi/event-bridge.ts:78-79 — Comment explains the length > 0 guard but not the continue it guards. Expand to: // Avoid extra yield when buffer is empty — continue re-checks before polling.

  • packages/providers/src/community/pi/provider.ts:449-455 — Multi-line comment restates the code (enableExtensions: false) and describes a UI path. Replace with: // Extensions default ON — opt out with \assistants.pi.enableExtensions: false`.`

  • packages/cli/src/commands/setup.ts:1714mode = modeChoice dropped the as SetupMode cast. Verify modeChoice's inferred type is SetupMode, add the cast back if not.

  • packages/providers/src/claude/provider.ts:28 — Commented-out type HookCallback import is dead code. Remove it.

  • packages/workflows/src/loader.ts:30-31const json = jsYaml.load(content); return json; is redundant. Replace with return jsYaml.load(content);.

  • packages/core/src/db/conversations.ts:1417-1429 — Note: when web conversation auto-creation fails, the user's message is silently dropped (null conv continues execution). This is pre-existing behavior consistent with conversation_lookup_failed above it — not introduced by this PR, but worth documenting as a known limitation.

Compliments

  • The Bun.YAMLjs-yaml migration is consistent across all three call sites (config-loader.ts, api.ts, loader.ts) — good DRY.
  • Error event naming throughout follows the {domain}.{action}_{state} pattern consistently.
  • The Pi event bridge has solid defensive error normalization (err instanceof Error ? err : new Error(String(err))) preventing protocol violations.

Reviewed via maintainer-review-pr workflow (Pi/Minimax). Aspects run: code-review, error-handling, test-coverage, comment-quality, docs-impact.

@worldomkar worldomkar closed this by deleting the head repository May 23, 2026
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.

3 participants