Bugfix/fix pi provider#1553
Conversation
…estrator-agent.ts with sessionnotfounderror skip logic
📝 WalkthroughWalkthroughThis PR adds Pi as a first-class AI provider option throughout the system. Changes include: updating database schema defaults from hardcoded ChangesPi Provider Support and Assistant Selection Refactoring
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
There was a problem hiding this comment.
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 winNormalize rejected values to real
Errorobjects (avoiderr as Error).
bridgeSessioncurrently doesqueue.push({ kind: 'error', error: err as Error })in both the subscription listener andpromptPromiserejection paths. If Pi rejects with a non-Errorvalue (e.g., string/object), the runtime value thrown viathrow item.errormay not be an actualError, which can break stack traces and any downstream error handling that assumesError.🛠️ 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 winTreat nullable codebase assistants as unset before the fallback.
remote_agent_codebases.ai_assistant_typeis now allowed to beNULL, but Line 104 assigns it directly and the fallback only checksassistantType === undefined. For a codebase row withNULL, this bypassesloadConfig().assistantand 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 winAsyncQueue should not treat
undefinedas “buffer empty” (latent generic correctness issue).
AsyncQueue.iterate()uses:
const next = this.buffer.shift();if (next !== undefined) { yield next; ... }Because
buffer.shift()returnsT | undefined, this conflates “queue item is actuallyundefined” with “no buffered item”. SinceAsyncQueueis generic, this can become a silent correctness bug if it’s ever used withTthat allowsundefinedin 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
📒 Files selected for processing (15)
.claude/docs/workflow-yaml-reference.mdmigrations/000_combined.sqlmigrations/001_initial_schema.sqlpackages/cli/src/commands/setup.tspackages/core/package.jsonpackages/core/src/config/config-loader.tspackages/core/src/db/codebases.tspackages/core/src/db/conversations.tspackages/core/src/handlers/clone.tspackages/core/src/orchestrator/orchestrator-agent.tspackages/providers/src/community/pi/event-bridge.tspackages/server/package.jsonpackages/server/src/routes/api.tspackages/workflows/package.jsonpackages/workflows/src/loader.ts
|
@coleam00 tagging for "pi-coding-agent" support. |
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.
There was a problem hiding this comment.
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
nullfrom DB bypasses theloadConfig()fallback
assistantTypeis initialized toundefined, but lines 86 and 104 assign it directly from query results, where the DB column can beNULLafter the migration that changed theai_assistant_typedefault toNULL. Strict=== undefinedon line 109 leavesassistantTypeasnulland insertsnullinto the new conversation, bypassing the intendedconfig.assistantfallback.🐛 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 winTwo 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'whenhasPi && hasCodex && !hasClaude:
At line 869–876,defaultAssistantis correctly computed fromenabledProviders(which includes'pi'). For example, ifprocess.env.DEFAULT_AI_ASSISTANT === 'pi'and the user selected codex + pi, the computed value is'pi'. The unconditionalelse if (hasCodex && !hasClaude)override on line 897 then clobbers that to'codex'.Bug 2 — Lines 879–884 filter to
p.builtInonly, excluding pi when all three are selected:
getRegisteredProviders().filter(p => p.builtIn)is evaluated only whenhasClaude && hasCodex. IfhasPiis 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 keepdefaultAssistantfrom the already-correct initial computation at line 869. All multi-provider combinations — includinghasPi && hasCodex && !hasClaudeandhasPi && 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 winTests depend on live
loadConfig()— non-deterministic against real config files or future built-in providers
getOrCreateConversationnow callsloadConfig()whenassistantTypeis stillundefined, but this test file doesn't mock../config/config-loader. The hardcoded'claude'expectation in this test (line 90) andfalls back to claude when codebase not found(line 167) will silently break if:
registerBuiltinProviders()insideloadConfig()registers any built-in provider whose ID isn't'claude'(the first built-in takes priority pergetDefaults()), or- a
~/.archon/config.yamlwithassistant: pi(or similar) is present in the test runner's home.Since
loadConfigis importable and onlyconfig.assistantis 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 onlyconfig.assistantis read bygetOrCreateConversation.🤖 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 forjs-yamlfor consistencyThe current default import
import jsYaml from 'js-yaml'is valid becauseesModuleInteropis 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
📒 Files selected for processing (8)
.claude/docs/workflow-yaml-reference.mdpackages/cli/src/commands/setup.tspackages/core/src/db/conversations.test.tspackages/core/src/db/conversations.tspackages/core/src/handlers/clone.tspackages/providers/src/index.tspackages/providers/src/registry.tspackages/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
Check buffer length before shifting to avoid undefined returns. This allows a safe type assertion and removes redundant checks.
|
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 |
|
@worldomkar related to #1541 — Pi provider chunking and invocation issues. |
|
Hi @worldomkar — thanks for opening this PR. This repository uses a PR template at
Could you fill those out (even briefly)? The template If a section genuinely doesn't apply, just write "N/A" in |
|
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: 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. |
|
Just checking: @coderabbitai can you provide answers to questions @Wirasm asked? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 4637
UX JourneyBefore After Architecture DiagramModules touched:
Label Snapshot
Change Metadata
Validation EvidenceThe author confirmed webchat interaction between Archon and pi works. However:
Security Impact
Compatibility / Migration
Human Verification
Side Effects / Blast Radius
Rollback Plan
Risks and Mitigations
|
|
@Wirasm seems simple comment worked 👍; just needs some automation. |
|
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. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 14772
Commits added after the working base
The three most likely culprits1.
|
|
@CodeRabbit, can you revert it to that commit? 🧐 |
|
Tip For best results, initiate chat on the files or code changes.
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-providerOption B — Keep the two safe fixes ( 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-providerOption 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
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! |
|
🔗 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. |
okay I have done a git revert already, |
There was a problem hiding this comment.
♻️ Duplicate comments (2)
packages/core/src/handlers/clone.ts (1)
49-52:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
DEFAULT_AI_ASSISTANTis 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-configuredpiwon’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 winConversation 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. Whenconvends upnull, the handler skips persistence at line 1431 and still callsdispatchToOrchestrator(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
getOrCreateConversationfails, or retryfindConversationByPlatformIdonce 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 winUse the cached
getLog()helper instead of re-creating the logger.Lines 12–15 define
getLog()specifically so test mocks can interceptcreateLogger. CallingcreateLogger('db.conversations')directly here defeats that pattern (and the rest of the file consistently usesgetLog()).♻️ 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 siblingdb.conversation_update_not_foundevent 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 winAdd validation for
assistantTypesourced from database rows.When
assistantTypeis set fromparent.rows[0].ai_assistant_typeorcodebase.rows[0].ai_assistant_type, it bypasses provider registration validation. The value only reachesisRegisteredProvider()indirectly if it remainsundefinedand falls back toloadConfig().assistant. A stale database row referencing a removed provider will propagate unchecked. Validate withisRegisteredProvider()and fallback toconfig.assistantwhen the stored value is unknown, matching the pattern used inconfig-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 winPin
jsYaml.dumpoptions for deterministic YAML serialization.Both calls at lines 2210 and 2340 use
jsYaml.dump(definition)with defaults that can introduce unwanted mutations:
lineWidth: 80wraps long strings, potentially altering workflow prompts/comments persisted to disknoRefs: falseemits YAML anchors/aliases for duplicate objects, which some downstream parsers handle inconsistentlyConsider 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 valueStale line-number references inside the
finallycomment.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
📒 Files selected for processing (10)
packages/cli/src/commands/setup.tspackages/core/src/db/conversations.tspackages/core/src/handlers/clone.tspackages/core/src/orchestrator/orchestrator-agent.tspackages/providers/src/claude/provider.tspackages/providers/src/community/pi/event-bridge.tspackages/providers/src/community/pi/provider.tspackages/server/package.jsonpackages/server/src/routes/api.tspackages/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
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 287117
What broke before
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)
Quick validations I recommend now
TL;DR
|
Review SummaryVerdict: blocking-issues This PR adds Pi as a first-class built-in provider and migrates all YAML parsing from Blocking issues
Suggested fixes
Minor / nice-to-have
Compliments
Reviewed via maintainer-review-pr workflow (Pi/Minimax). Aspects run: code-review, error-handling, test-coverage, comment-quality, docs-impact. |
Summary
Describe this PR in 2-5 bullets:
Linked Issue
config in ~/.archon/config.yaml that uses pi's model from ~/.pi/agent/models.json
Summary by CodeRabbit
New Features
Bug Fixes
Improvements