Skip to content

fix: show real platform adapter status on Settings page (#1031)#1032

Closed
CryptixSamurai wants to merge 25 commits intocoleam00:devfrom
CryptixSamurai:fix/platform-status-display
Closed

fix: show real platform adapter status on Settings page (#1031)#1032
CryptixSamurai wants to merge 25 commits intocoleam00:devfrom
CryptixSamurai:fix/platform-status-display

Conversation

@CryptixSamurai
Copy link
Copy Markdown

@CryptixSamurai CryptixSamurai commented Apr 10, 2026

Summary

Fixes #1031 — The Settings page showed all platform adapters (Slack, Telegram, Discord, GitHub) as "Not configured" even when they were actively running.

Root cause: Statuses were hardcoded to false in PlatformConnectionsSection, and the /api/health endpoint didn't expose adapter state.

Changes:

  • Add ActiveAdapters interface and adapters field to /api/health response
  • Pass real adapter state from index.ts via lazy callback (needed because Telegram initializes after server.listen())
  • Update frontend HealthResponse type with optional adapters field (backward-compatible)
  • Update PlatformConnectionsSection to display real adapter status with graceful fallback

Test plan

  • bun run type-check — all packages pass
  • bun run lint — zero warnings
  • bun run test — all tests pass
  • Manual: curl /api/health returns adapters: { slack: true, telegram: true, github: true, ... }
  • Manual: Settings page shows "Connected" for Slack, Telegram, GitHub
  • Verify backward compatibility: frontend handles missing adapters field gracefully (older server versions)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Settings page now shows accurate connection status for Slack, Telegram, Discord, GitHub, Gitea and GitLab.
  • New Features

    • Health API exposes per-platform adapter availability.
    • Telegram forum-thread support for targeted messages and conversation tracking.
    • Added /setproject, /compact and /resume slash commands to manage project binding and conversation summaries.
    • Conversation summaries stored and loadable across sessions (migration applied).
  • Documentation

    • New Beads usage guide and agent workflow docs.
  • Chores

    • Ignore rules updated to exclude Beads/Dolt artifacts and credential files; repo-local Beads tooling and Git hook integration added.

The Platform Connections section showed all adapters as "Not configured"
because statuses were hardcoded to false. The /api/health endpoint also
lacked adapter state information.

- Add `adapters` field to health endpoint with real adapter status
- Pass adapter state from index.ts via lazy callback (Telegram initializes
  after server.listen(), so eager read would miss it)
- Update frontend HealthResponse type and PlatformConnectionsSection to
  consume real adapter data with optional fallback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds runtime adapter availability reporting: the health API now returns an adapters object evaluated via a lazy callback from server initialization; frontend types and Settings page consume these flags. Also adds Telegram thread support, new orchestrator commands and conversation context summary storage, plus Beads repo tooling and many git hooks.

Changes

Cohort / File(s) Summary
API Health & Types
packages/server/src/routes/api.ts, packages/web/src/lib/api.ts
Add adaptersSchema and exported ActiveAdapters; extend /api/health response to include adapters; export/consume adapters in frontend HealthResponse.
Server Init / Route Registration
packages/server/src/index.ts
Pass a lazy getActiveAdapters callback into registerApiRoutes so adapter availability is evaluated at request time from runtime adapter variables.
Frontend Settings UI
packages/web/src/routes/SettingsPage.tsx
PlatformConnectionsSection accepts adapters prop; platform connected statuses read from health?.adapters instead of hardcoded false.
Telegram Adapter (threads & logging)
packages/adapters/src/chat/telegram/adapter.ts
Support chatId:threadId parsing, include message_thread_id when sending chunks, return conversation IDs with thread suffixes, and add thread-aware logging.
Orchestrator & Commands
packages/core/src/orchestrator/orchestrator-agent.ts, packages/core/src/handlers/command-handler.ts
Add /compact, /resume, /setproject deterministic command handling; persist and use conversation.context_summary in prompts; update help text.
DB Schema & Conversation API
migrations/022_add_context_summary.sql, migrations/000_combined.sql, packages/core/src/db/adapters/sqlite.ts, packages/core/src/db/conversations.ts, packages/core/src/types/index.ts
Add nullable context_summary to remote_agent_conversations; migration and CREATE TABLE updated; add updateConversationSummary() DB API; add Conversation.context_summary type.
Repo Tooling & Hooks
.beads/..., .beads/hooks/..., .beads/config.yaml, .beads/README.md, .beads/metadata.json, AGENTS.md, CLAUDE.md, .gitignore
Add Beads issue-tracking files, many git hook scripts and helpers, configuration, docs and gitignore entries (large, unrelated tooling/docs addition).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server
  participant AdapterState as AdapterManager

  Client->>Server: GET /api/health
  Server->>AdapterState: call getActiveAdapters()
  AdapterState-->>Server: { slack: bool, telegram: bool, discord: bool, github: bool, gitea: bool, gitlab: bool }
  Server-->>Client: 200 OK { status: "...", adapters: { ... } }
  Client->>Client: render PlatformConnectionsSection using adapters
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibbled logs and found the threads,

Adapters wake where truth was spread,
Health speaks clear at request-time calls,
Settings bloom in connected halls,
Hooray — the rabbit hops, fixes led!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes significant out-of-scope additions: new .beads/ directory with Beads issue tracker configuration, hook scripts, documentation (AGENTS.md, CLAUDE.md), and Claude orchestrator rules unrelated to platform adapter status display. Remove .beads/ directory contents, AGENTS.md, CLAUDE.md, and .claude/rules/orchestrator.md changes—these belong in separate PRs focused on Beads integration and agent workflow documentation.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main change: exposing and displaying real platform adapter status on the Settings page instead of hardcoded false values.
Description check ✅ Passed The description covers the problem, root cause, changes made, and test plan. It includes type-checking, linting, and manual testing results with backward compatibility considerations documented.
Linked Issues check ✅ Passed The PR fully addresses issue #1031 by adding adapters field to /api/health response, passing real adapter state via lazy callback, updating frontend types, and implementing graceful fallback.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/server/src/routes/api.ts`:
- Around line 808-817: adaptersSchema is defined inline and a separate
ActiveAdapters type duplicates its shape; move the Zod schema into the route
schemas module (the central schemas directory) as a shared export (e.g., export
const adaptersSchema) and replace the duplicated handcrafted type with type
ActiveAdapters = z.infer<typeof adaptersSchema>; update imports in the route
file to import adaptersSchema and remove the redundant definition and any other
duplicated schemas (the similar duplicate at the other occurrence) so all routes
derive types from the single source-of-truth schema.
🪄 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: ee24ca51-b5f4-4b0d-bdd1-e146d8c3cd66

📥 Commits

Reviewing files that changed from the base of the PR and between 95679fa and ac8826d.

📒 Files selected for processing (4)
  • packages/server/src/index.ts
  • packages/server/src/routes/api.ts
  • packages/web/src/lib/api.ts
  • packages/web/src/routes/SettingsPage.tsx

Comment on lines +808 to +817
const adaptersSchema = z
.object({
slack: z.boolean(),
telegram: z.boolean(),
discord: z.boolean(),
github: z.boolean(),
gitea: z.boolean(),
gitlab: z.boolean(),
})
.openapi('Adapters');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Move adaptersSchema to route schemas and derive ActiveAdapters from it.

The schema is currently defined inline in the route module, and ActiveAdapters duplicates the same shape. This is drift-prone and breaks repo conventions.

♻️ Proposed refactor
-const adaptersSchema = z
-  .object({
-    slack: z.boolean(),
-    telegram: z.boolean(),
-    discord: z.boolean(),
-    github: z.boolean(),
-    gitea: z.boolean(),
-    gitlab: z.boolean(),
-  })
-  .openapi('Adapters');
+import { adaptersSchema } from './schemas/config.schemas';

-export interface ActiveAdapters {
-  slack: boolean;
-  telegram: boolean;
-  discord: boolean;
-  github: boolean;
-  gitea: boolean;
-  gitlab: boolean;
-}
+export type ActiveAdapters = z.infer<typeof adaptersSchema>;

As per coding guidelines: "Route schemas live in packages/server/src/routes/schemas/ — one file per domain" and "Always use z.infer<typeof schema> to derive types from Zod schemas; never write parallel hand-crafted interfaces".

Also applies to: 849-857

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

In `@packages/server/src/routes/api.ts` around lines 808 - 817, adaptersSchema is
defined inline and a separate ActiveAdapters type duplicates its shape; move the
Zod schema into the route schemas module (the central schemas directory) as a
shared export (e.g., export const adaptersSchema) and replace the duplicated
handcrafted type with type ActiveAdapters = z.infer<typeof adaptersSchema>;
update imports in the route file to import adaptersSchema and remove the
redundant definition and any other duplicated schemas (the similar duplicate at
the other occurrence) so all routes derive types from the single source-of-truth
schema.

skundu42 pushed a commit to skundu42/Archon that referenced this pull request Apr 10, 2026
…leam00#1032)

* fix(codex): emit paired tool_result chunks so UI tool cards close

Tool cards in the web UI sometimes spin forever for Codex workflows.
The Codex client only yielded { type: 'tool', ... } on item.completed
events, never the paired tool_result chunk. The web adapter's running
tool entry then had nothing to close it, leaving the UI relying on the
emitLockEvent fallback at lock release — which never fires inside a
multi-node DAG, on cancel, or when SSE is briefly disconnected.

The Codex SDK only emits item.completed once a command_execution,
web_search, or mcp_tool_call is fully done (it carries aggregated_output,
exit_code, status, etc). So we can emit the start and the result
back-to-back in the same handler.

Changes:
- command_execution: emit tool_result with aggregated_output, append
  [exit code: N] when non-zero so failures are visible.
- web_search: emit empty tool_result so the searching card closes.
- mcp_tool_call: always emit tool + tool_result, including for the
  status === 'completed' branch which previously emitted nothing at all
  (so completed MCP calls were invisible) and for status === 'failed'
  where we previously emitted only a system message (leaving no card to
  close, but inconsistent with command_execution failures).
- Update codex.test.ts assertions to cover paired chunks and exit codes.

Note: tool_result is paired to its tool by the web adapter's name-based
reverse-scan in web.ts. Since these chunks are yielded back-to-back with
no other tools in between, the match is unambiguous. PR coleam00#1031 will add
stable tool_use_id pairing for Claude; a follow-up can plumb Codex's
item.id through once that lands.

* fix(codex): log silent drops and assert paired web_search tool_result

- command_execution: warn when item.command is falsy (was silently dropped)
- mcp_tool_call: warn when result.content has unexpected shape (was silent empty)
- Simplify exit_code guard to != null, drop redundant String() cast
- Test: assert paired tool_result chunk for web_search

Addresses review feedback on coleam00#1032.
skundu42 pushed a commit to skundu42/Archon that referenced this pull request Apr 10, 2026
… tool_results (coleam00#1037)

* fix(sse): extend buffer TTL beyond reconnect grace to prevent dropped tool_results

The SSE event buffer held events for only 3s, but the conversation
reconnect grace period is 5s — meaning events emitted during a
reconnect window could expire *before* the client even had a chance
to reconnect. When a tool_result happened to land in that gap, the
UI would show a perpetually spinning tool card with no recovery path.

This is one of the remaining causes from the 'tool cards stuck
running' investigation. The two biggest causes (Claude hook coverage
and Codex tool_result emission) were already fixed in coleam00#1031 and coleam00#1032.
This closes the last high-impact backend gap.

Changes:
- EVENT_BUFFER_TTL_MS: 3_000 → 60_000. Covers typical EventSource
  auto-reconnect delays on flaky networks (mobile, VPN, laptop sleep).
- EVENT_BUFFER_MAX: 50 → 500. Events are small JSON strings; 500
  bounds worst-case memory while giving real headroom for bursts.
- Warn when buffer cap evicts oldest (previously silent).
- Warn when events expire on TTL at replay time (previously silent).
  Both warnings give us observability if the new bounds are still
  ever insufficient.

Note: a full Last-Event-ID resume protocol would be more principled
but requires monotonic event IDs and client-side offset tracking —
a larger change with its own risks. The TTL bump alone closes the
vast majority of the window at near-zero cost.

* fix(sse): throttle eviction warns, reset cleanup timer, enforce TTL invariant

Address review feedback on the SSE buffer TTL bump:

- Reset the buffer cleanup timer on each new event so the buffer is held
  for TTL past the most recent event, not the first one. With the 20x TTL
  bump this gap became meaningful — a fresh event could be wiped by a
  cleanup timer scheduled when the first (now-stale) event was buffered.

- Throttle 'transport.buffer_evicted_oldest' warns to one per conversation
  per 5s. A runaway producer overflowing the cap by hundreds would
  otherwise flood logs.

- Fail-fast at module load if EVENT_BUFFER_TTL_MS < RECONNECT_GRACE_MS.
  Locks in the invariant the comment already documents.

- Add test covering the eviction-warn throttle.
lolipoplol and others added 4 commits April 10, 2026 15:30
beads `bd remember` was pushing to upstream fork (403), causing memory
loss. MEMORY.md files were prohibited but beads couldn't replace them.
Now: Claude Code memory (MEMORY.md) for persistent knowledge,
beads only for issue tracking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each forum topic gets a unique conversation ID (chatId:threadId),
enabling per-topic conversation isolation and project binding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolves coleam00#1044. Adds deterministic /setproject <name>
command that updates conversation.codebase_id and cwd, enabling
per-topic project binding in Telegram Forum Topics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
.claude/settings.json (1)

1-83: Consider splitting this config-only hook reshuffle from the platform-status fix PR.

These .claude lifecycle hook changes look unrelated to issue #1031’s runtime adapter status objective. Splitting them into a separate PR would keep scope tight and reduce review/rollback risk.

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

In @.claude/settings.json around lines 1 - 83, The .claude/settings.json
lifecycle hook changes (modify Notification, PreCompact, SessionStart, Stop,
SubagentStop, UserPromptSubmit hook entries) are unrelated to the runtime
adapter status fix and should be split out: revert or remove the hook edits from
this PR and create a separate branch/PR that contains only the
.claude/settings.json changes (or vice versa—keep only runtime-adapter changes
here and move the hook edits to a new PR) so the platform-status fix stays
narrowly scoped and the hook reshuffle can be reviewed/rolled back
independently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.beads/hooks/h:
- Around line 4-6: The hook shim script h currently calls exit (terminating the
parent hook) when a target script is missing, so BEADS integration in the actual
hook shims (pre-commit, post-merge, post-checkout) never runs; fix by either
ensuring target scripts are installed at .beads/pre-commit, .beads/post-merge,
.beads/post-checkout, etc., or change the logic in h to avoid exiting the
parent: detect the intended .beads/<hook> target and if it doesn't exist, do not
call exit — instead return (or skip sourcing) so the calling hook shim can
continue to run its BEADS integration code; update h and the hook shims
(pre-commit, post-merge, post-checkout) to use this conditional load pattern.

In @.beads/hooks/pre-push:
- Around line 2-26: The beads integration block is never reached because the
Husky helper is sourced unconditionally (". \"$(dirname \"$0\")/h\"") and that
helper calls "exit $c", terminating the hook; move the entire beads block (the
lines between "# --- BEGIN BEADS INTEGRATION v1.0.0 ---" and "# --- END BEADS
INTEGRATION v1.0.0 ---") above the sourcing of the Husky helper in each hook
(pre-push, post-merge, prepare-commit-msg, pre-commit, post-checkout), or
alternatively avoid sourcing the helper into the current shell by executing it
in a subshell/isolated context instead of ". \"$(dirname \"$0\")/h\"" so that
the helper's "exit $c" cannot abort the rest of the hook; adjust all five hook
files consistently.

---

Nitpick comments:
In @.claude/settings.json:
- Around line 1-83: The .claude/settings.json lifecycle hook changes (modify
Notification, PreCompact, SessionStart, Stop, SubagentStop, UserPromptSubmit
hook entries) are unrelated to the runtime adapter status fix and should be
split out: revert or remove the hook edits from this PR and create a separate
branch/PR that contains only the .claude/settings.json changes (or vice
versa—keep only runtime-adapter changes here and move the hook edits to a new
PR) so the platform-status fix stays narrowly scoped and the hook reshuffle can
be reviewed/rolled back independently.
🪄 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: c4771a8c-5466-4991-ad79-9b9caff16d6a

📥 Commits

Reviewing files that changed from the base of the PR and between ac8826d and d50162a.

📒 Files selected for processing (24)
  • .beads/.gitignore
  • .beads/README.md
  • .beads/config.yaml
  • .beads/hooks/applypatch-msg
  • .beads/hooks/commit-msg
  • .beads/hooks/h
  • .beads/hooks/husky.sh
  • .beads/hooks/post-applypatch
  • .beads/hooks/post-checkout
  • .beads/hooks/post-commit
  • .beads/hooks/post-merge
  • .beads/hooks/post-rewrite
  • .beads/hooks/pre-applypatch
  • .beads/hooks/pre-auto-gc
  • .beads/hooks/pre-commit
  • .beads/hooks/pre-merge-commit
  • .beads/hooks/pre-push
  • .beads/hooks/pre-rebase
  • .beads/hooks/prepare-commit-msg
  • .beads/metadata.json
  • .claude/settings.json
  • .gitignore
  • AGENTS.md
  • CLAUDE.md
✅ Files skipped from review due to trivial changes (17)
  • .gitignore
  • .beads/hooks/applypatch-msg
  • .beads/hooks/pre-rebase
  • .beads/hooks/post-applypatch
  • .beads/hooks/commit-msg
  • .beads/hooks/pre-merge-commit
  • .beads/metadata.json
  • .beads/hooks/pre-applypatch
  • .beads/hooks/pre-auto-gc
  • .beads/hooks/post-commit
  • .beads/hooks/post-rewrite
  • .beads/hooks/husky.sh
  • .beads/README.md
  • .beads/config.yaml
  • AGENTS.md
  • .beads/.gitignore
  • CLAUDE.md

Comment thread .beads/hooks/h
Comment on lines +4 to +6
s=$(dirname "$(dirname "$0")")/$n

[ ! -f "$s" ] && exit 0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

missing=0
while IFS= read -r hook_path; do
  hook_name="$(basename "$hook_path")"
  case "$hook_name" in
    h|husky.sh) continue ;;
  esac

  target="$(dirname "$(dirname "$hook_path")")/$hook_name"
  if [ -f "$target" ]; then
    printf 'OK      %s -> %s\n' "$hook_path" "$target"
  else
    printf 'MISSING %s -> %s\n' "$hook_path" "$target"
    missing=1
  fi
done < <(fd -t f . .beads/hooks)

exit "$missing"

Repository: coleam00/Archon

Length of output: 116


🏁 Script executed:

# Examine the hook helper script
cat -n .beads/hooks/h

# List all hook files
ls -la .beads/hooks/ 2>/dev/null || echo "Directory not found"

# Check if parent-level scripts exist
ls -la .beads/ 2>/dev/null || echo "Parent directory check"

Repository: coleam00/Archon

Length of output: 2255


🏁 Script executed:

# Check what target scripts exist in parent .beads directory
echo "=== Checking for target scripts in .beads/ ==="
ls -la .beads/ | grep -v '^d' | grep -v '^total'

echo ""
echo "=== Expected target scripts for each hook ==="
cd .beads/hooks
for hook_file in *; do
  case "$hook_file" in
    h|husky.sh) continue ;;
  esac
  target=".beads/$hook_file"
  if [ -f "../$hook_file" ]; then
    echo "$hook_file has target: ../$hook_file"
  else
    echo "$hook_file MISSING target: ../$hook_file"
  fi
done

Repository: coleam00/Archon

Length of output: 1065


🏁 Script executed:

# Search for the actual target scripts that the hooks expect to call
echo "=== Searching for hook target scripts in repository root ==="
find . -maxdepth 1 -type f \( -name "pre-commit" -o -name "post-commit" -o -name "post-merge" \) 2>/dev/null

echo ""
echo "=== All shell scripts in root ==="
find . -maxdepth 1 -type f -name "*.sh" 2>/dev/null

echo ""
echo "=== Checking if hooks are git hooks installed in .git/hooks ==="
if [ -d ".git/hooks" ]; then
  ls -la .git/hooks/ 2>/dev/null | head -20
else
  echo ".git/hooks not found in sandbox"
fi

echo ""
echo "=== Examining one of the small hook files to understand the shimming pattern ==="
cat .beads/hooks/pre-commit
</script>

Repository: coleam00/Archon

Length of output: 1420


🏁 Script executed:

# Let me check if the beads integration code is actually reached
# by understanding the sourcing behavior

echo "=== Checking the flow in detail ==="
echo "When a git hook runs .beads/hooks/pre-commit:"
echo "1. .beads/hooks/pre-commit sources h via: . \$(dirname \"\$0\")/h"
echo "2. h tries to execute target at .beads/pre-commit"
echo "3. If target doesn't exist, h calls 'exit 0'"
echo ""
echo "Question: Does 'exit 0' in a sourced script terminate the parent?"
echo ""
echo "=== Checking if there are beads hook handlers elsewhere ==="
fd -t f "hooks" . --exclude node_modules 2>/dev/null | grep -E "(run|handler)" | head -20

echo ""
echo "=== Check .bd directory or beads config ==="
if [ -d ".bd" ]; then
  find .bd -type f | head -10
else
  echo ".bd directory not found"
fi

echo ""
echo "=== Examine beads config ==="
cat .beads/config.yaml

Repository: coleam00/Archon

Length of output: 2612


🏁 Script executed:

# Verify critical behavior: what happens when exit 0 is called in a sourced script?
# This is the key to understanding if hooks are truly bypassed

echo "=== Test: Does 'exit' in sourced script terminate parent? ==="
# Create test files to verify
mkdir -p /tmp/test_sourcing
cd /tmp/test_sourcing

cat > helper.sh << 'EOF'
#!/bin/sh
echo "In helper before exit"
exit 0
echo "In helper after exit (should not print)"
EOF

cat > main.sh << 'EOF'
#!/bin/sh
echo "Main: before sourcing"
. ./helper.sh
echo "Main: after sourcing (should not print if exit terminates parent)"
EOF

chmod +x helper.sh main.sh
sh main.sh
echo "Exit code: $?"

# Now go back and verify the actual hook behavior in the repository
cd - > /dev/null
echo ""
echo "=== Simulating actual git hook execution ==="
echo "When git runs: .git/hooks/pre-commit (symlink to .beads/hooks/pre-commit)"
echo "The script does: . \$(dirname \"\$0\")/h"
echo ""
echo "Inside h (line 6): [ ! -f \"\$s\" ] && exit 0"
echo "Since .beads/pre-commit doesn't exist, this exits with 0"
echo ""
echo "This means the BEADS INTEGRATION code in .beads/hooks/pre-commit is NEVER reached"
echo "because exit 0 in the sourced h script terminates the parent script immediately."

Repository: coleam00/Archon

Length of output: 599


Critical: Git hooks are non-functional due to missing target scripts causing premature exit.

Line 6 exits with status 0 when the target script is absent. Since no target scripts exist in .beads/ (only .beads/hooks/), every hook immediately exits before reaching the BEADS integration code. This silently disables hook functionality.

The .beads/hooks/pre-commit and other hook shims contain BEADS integration logic (lines 7–21+) that will never execute because the sourced h script terminates the parent before reaching that code.

Ensure target scripts are installed at .beads/pre-commit, .beads/post-merge, .beads/post-checkout, etc., or restructure the hook architecture to conditionally load BEADS integration only when targets exist.

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

In @.beads/hooks/h around lines 4 - 6, The hook shim script h currently calls
exit (terminating the parent hook) when a target script is missing, so BEADS
integration in the actual hook shims (pre-commit, post-merge, post-checkout)
never runs; fix by either ensuring target scripts are installed at
.beads/pre-commit, .beads/post-merge, .beads/post-checkout, etc., or change the
logic in h to avoid exiting the parent: detect the intended .beads/<hook> target
and if it doesn't exist, do not call exit — instead return (or skip sourcing) so
the calling hook shim can continue to run its BEADS integration code; update h
and the hook shims (pre-commit, post-merge, post-checkout) to use this
conditional load pattern.

Comment thread .beads/hooks/pre-push
Comment on lines +2 to +26
. "$(dirname "$0")/h"

# --- BEGIN BEADS INTEGRATION v1.0.0 ---
# This section is managed by beads. Do not remove these markers.
if command -v bd >/dev/null 2>&1; then
export BD_GIT_HOOK=1
_bd_timeout=${BEADS_HOOK_TIMEOUT:-300}
if command -v timeout >/dev/null 2>&1; then
timeout "$_bd_timeout" bd hooks run pre-push "$@"
_bd_exit=$?
if [ $_bd_exit -eq 124 ]; then
echo >&2 "beads: hook 'pre-push' timed out after ${_bd_timeout}s — continuing without beads"
_bd_exit=0
fi
else
bd hooks run pre-push "$@"
_bd_exit=$?
fi
if [ $_bd_exit -eq 3 ]; then
echo >&2 "beads: database not initialized — skipping hook 'pre-push'"
_bd_exit=0
fi
if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi
fi
# --- END BEADS INTEGRATION v1.0.0 ---
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that hook wrapper code is unreachable due to `exit` in sourced helper.

set -euo pipefail

echo "== .beads/hooks/h (look for exit statements) =="
sed -n '1,80p' .beads/hooks/h

echo
echo "== .beads/hooks/pre-push (source first, integration after) =="
sed -n '1,80p' .beads/hooks/pre-push

echo
echo "== Cross-check all added hook wrappers with same pattern =="
for f in pre-push post-merge prepare-commit-msg pre-commit post-checkout; do
  echo "--- .beads/hooks/$f ---"
  sed -n '1,40p' ".beads/hooks/$f"
done

Repository: coleam00/Archon

Length of output: 5826


Beads integration block is unreachable due to unconditional exit in sourced helper.

On line 2, . "$(dirname "$0")/h" sources a Husky helper that always exits (line 19: exit $c). Since sourcing executes the script in the current shell context, this unconditional exit terminates the hook before lines 4–26 run. The Beads integration block never executes.

This pattern affects all five hook files: pre-push, post-merge, prepare-commit-msg, pre-commit, and post-checkout. Either the Beads integration must be inserted before sourcing h, or h must be sourced only after the Beads integration block completes.

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

In @.beads/hooks/pre-push around lines 2 - 26, The beads integration block is
never reached because the Husky helper is sourced unconditionally (".
\"$(dirname \"$0\")/h\"") and that helper calls "exit $c", terminating the hook;
move the entire beads block (the lines between "# --- BEGIN BEADS INTEGRATION
v1.0.0 ---" and "# --- END BEADS INTEGRATION v1.0.0 ---") above the sourcing of
the Husky helper in each hook (pre-push, post-merge, prepare-commit-msg,
pre-commit, post-checkout), or alternatively avoid sourcing the helper into the
current shell by executing it in a subshell/isolated context instead of ".
\"$(dirname \"$0\")/h\"" so that the helper's "exit $c" cannot abort the rest of
the hook; adjust all five hook files consistently.

updateConversation() expects the internal UUID (conversation.id),
not the platform_conversation_id string. Fixes "Conversation not found"
error in Telegram forum topics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
.claude/rules/orchestrator.md (1)

32-48: ⚠️ Potential issue | 🟡 Minor

Fix deterministic-command count consistency across the doc.

This section correctly says 11 commands, but Line 122 still says “only the 10 listed above bypass the AI router.” Please update that line too, otherwise the rule doc contradicts itself.

Suggested doc fix
-- Never assume a slash command is deterministic — only the 10 listed above bypass the AI router
+- Never assume a slash command is deterministic — only the 11 listed above bypass the AI router
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/rules/orchestrator.md around lines 32 - 48, Update the inconsistent
count in the orchestrator rules doc: change the phrase "only the 10 listed above
bypass the AI router." to reflect 11 deterministic commands so it matches the
header that lists 11 commands; ensure the wording references the same set
(including `/help`, `/status`, `/reset`, `/workflow`, `/register-project`,
`/update-project`, `/remove-project`, `/setproject`, `/commands`, `/init`,
`/worktree`) so the document is consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/adapters/src/chat/telegram/adapter.ts`:
- Around line 241-244: The receipt log is emitting raw chat identifiers
(ctx.chat?.id and conversationId) at info level in the getLog().info call for
'telegram.message_received'; change this to mask those identifiers the same way
userId is masked earlier (or omit the full values) before logging—e.g., replace
ctx.chat?.id and conversationId with the masked versions used elsewhere (or a
maskId() helper) and keep threadId as-is or masked if sensitive so the
info-level log never contains raw platform IDs.
- Around line 83-89: The parseChatId function currently permissively uses
parseInt and can accept malformed strings; update parseChatId to validate the
input shape before parsing by enforcing the exact "chatId" or "chatId:threadId"
format (no extra segments, only digits), e.g. check the string matches a strict
pattern and that parts.length is 1 or 2 and each part is /^\d+$/; if validation
fails, throw a clear error describing the expected format; then convert the
validated parts to numbers for numericChatId and optional threadId to avoid
misrouting or downstream Telegraf errors.

In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 686-691: The /setproject flow is passing the platform
conversationId into handleSetProject but db.updateConversation expects the DB
conversation row id, causing the project bind to fail; fix by resolving the DB
row id before updating or by changing handleSetProject to accept/lookup the DB
id: either call a helper like db.getConversationByPlatformId(conversationId) (or
getConversationByPlatformId) in orchestrator-agent and pass that
dbConversation.id into handleSetProject (or return it) so that inside
handleSetProject / db.updateConversation uses the DB row id; ensure
platform.sendMessage still uses the platform conversationId while all
db.updateConversation calls use the resolved dbConversation.id.

---

Outside diff comments:
In @.claude/rules/orchestrator.md:
- Around line 32-48: Update the inconsistent count in the orchestrator rules
doc: change the phrase "only the 10 listed above bypass the AI router." to
reflect 11 deterministic commands so it matches the header that lists 11
commands; ensure the wording references the same set (including `/help`,
`/status`, `/reset`, `/workflow`, `/register-project`, `/update-project`,
`/remove-project`, `/setproject`, `/commands`, `/init`, `/worktree`) so the
document is consistent.
🪄 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: 14e15e3b-3360-4bc0-bcad-fbd3f820df0e

📥 Commits

Reviewing files that changed from the base of the PR and between d50162a and fef52c6.

📒 Files selected for processing (4)
  • .claude/rules/orchestrator.md
  • packages/adapters/src/chat/telegram/adapter.ts
  • packages/core/src/handlers/command-handler.ts
  • packages/core/src/orchestrator/orchestrator-agent.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/core/src/handlers/command-handler.ts

Comment on lines +83 to +89
private parseChatId(chatId: string): { numericChatId: number; threadId: number | undefined } {
const parts = chatId.split(':');
return {
numericChatId: parseInt(parts[0]),
threadId: parts[1] ? parseInt(parts[1]) : undefined,
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate the composite Telegram conversation ID before parsing.

parseInt() is permissive here, so malformed inputs like 123abc:7 or 1:2:3 can be partially accepted and either misroute a reply or fail later inside Telegraf. This should reject anything outside the exact "chatId" / "chatId:threadId" shape up front.

Suggested fix
   private parseChatId(chatId: string): { numericChatId: number; threadId: number | undefined } {
-    const parts = chatId.split(':');
-    return {
-      numericChatId: parseInt(parts[0]),
-      threadId: parts[1] ? parseInt(parts[1]) : undefined,
-    };
+    const match = /^(-?\d+)(?::(\d+))?$/.exec(chatId);
+    if (!match) {
+      throw new Error(`Invalid Telegram conversation ID: ${chatId}`);
+    }
+
+    return {
+      numericChatId: Number(match[1]),
+      threadId: match[2] ? Number(match[2]) : undefined,
+    };
   }

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

📝 Committable suggestion

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

Suggested change
private parseChatId(chatId: string): { numericChatId: number; threadId: number | undefined } {
const parts = chatId.split(':');
return {
numericChatId: parseInt(parts[0]),
threadId: parts[1] ? parseInt(parts[1]) : undefined,
};
}
private parseChatId(chatId: string): { numericChatId: number; threadId: number | undefined } {
const match = /^(-?\d+)(?::(\d+))?$/.exec(chatId);
if (!match) {
throw new Error(`Invalid Telegram conversation ID: ${chatId}`);
}
return {
numericChatId: Number(match[1]),
threadId: match[2] ? Number(match[2]) : undefined,
};
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/adapters/src/chat/telegram/adapter.ts` around lines 83 - 89, The
parseChatId function currently permissively uses parseInt and can accept
malformed strings; update parseChatId to validate the input shape before parsing
by enforcing the exact "chatId" or "chatId:threadId" format (no extra segments,
only digits), e.g. check the string matches a strict pattern and that
parts.length is 1 or 2 and each part is /^\d+$/; if validation fails, throw a
clear error describing the expected format; then convert the validated parts to
numbers for numericChatId and optional threadId to avoid misrouting or
downstream Telegraf errors.

Comment on lines +241 to +244
// Debug: log forum topic detection
const msg = ctx.message;
const threadId = 'message_thread_id' in msg ? (msg as { message_thread_id?: number }).message_thread_id : undefined;
getLog().info({ chatId: ctx.chat?.id, threadId, conversationId, chatType: ctx.chat?.type }, 'telegram.message_received');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Mask chat identifiers in the receipt log.

This emits raw chatId and conversationId at info level for every incoming message. Those are stable platform identifiers, so this creates routine identifier leakage in logs. Mask them the same way userId is masked above, or keep full values out of info-level logs.

Suggested fix
-        const threadId = 'message_thread_id' in msg ? (msg as { message_thread_id?: number }).message_thread_id : undefined;
-        getLog().info({ chatId: ctx.chat?.id, threadId, conversationId, chatType: ctx.chat?.type }, 'telegram.message_received');
+        const threadId = 'message_thread_id' in msg ? (msg as { message_thread_id?: number }).message_thread_id : undefined;
+        const maskedChatId = ctx.chat ? `${String(ctx.chat.id).slice(0, 4)}***` : undefined;
+        const maskedConversationId = `${conversationId.slice(0, 4)}***`;
+        getLog().info(
+          { chatId: maskedChatId, threadId, conversationId: maskedConversationId, chatType: ctx.chat?.type },
+          'telegram.message_received'
+        );

As per coding guidelines, "Never log API keys, tokens (mask sensitive values), user message content, or PII in logs".

📝 Committable suggestion

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

Suggested change
// Debug: log forum topic detection
const msg = ctx.message;
const threadId = 'message_thread_id' in msg ? (msg as { message_thread_id?: number }).message_thread_id : undefined;
getLog().info({ chatId: ctx.chat?.id, threadId, conversationId, chatType: ctx.chat?.type }, 'telegram.message_received');
// Debug: log forum topic detection
const msg = ctx.message;
const threadId = 'message_thread_id' in msg ? (msg as { message_thread_id?: number }).message_thread_id : undefined;
const maskedChatId = ctx.chat ? `${String(ctx.chat.id).slice(0, 4)}***` : undefined;
const maskedConversationId = `${conversationId.slice(0, 4)}***`;
getLog().info(
{ chatId: maskedChatId, threadId, conversationId: maskedConversationId, chatType: ctx.chat?.type },
'telegram.message_received'
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/adapters/src/chat/telegram/adapter.ts` around lines 241 - 244, The
receipt log is emitting raw chat identifiers (ctx.chat?.id and conversationId)
at info level in the getLog().info call for 'telegram.message_received'; change
this to mask those identifiers the same way userId is masked earlier (or omit
the full values) before logging—e.g., replace ctx.chat?.id and conversationId
with the masked versions used elsewhere (or a maskId() helper) and keep threadId
as-is or masked if sensitive so the info-level log never contains raw platform
IDs.

Comment thread packages/core/src/orchestrator/orchestrator-agent.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

1282-1307: Rename conversationId to conversationDbId for clarity.

This function now expects a DB row ID, not a platform conversation ID. Renaming the parameter reduces ambiguity and helps prevent regressions.

Suggested refactor
-async function handleSetProject(message: string, conversationId: string): Promise<string> {
+async function handleSetProject(message: string, conversationDbId: string): Promise<string> {
@@
-  await db.updateConversation(conversationId, {
+  await db.updateConversation(conversationDbId, {
     codebase_id: codebase.id,
     cwd: codebase.default_cwd,
   });
@@
-    { conversationId, projectName: codebase.name, codebaseId: codebase.id },
+    { conversationId: conversationDbId, projectName: codebase.name, codebaseId: codebase.id },
     'project.setproject_completed'
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 1282 -
1307, Rename the parameter conversationId to conversationDbId in the
handleSetProject function signature and all its usages within the function
(including the call to db.updateConversation and the getLog().info call) to
reflect that this is a database row ID rather than a platform conversation ID;
update the parameter name in the signature async function
handleSetProject(message: string, conversationDbId: string): Promise<string> and
replace conversationId with conversationDbId in commandHandler.parseCommand
result handling, the db.updateConversation payload, and the logging call
(getLog().info) to keep names consistent and avoid ambiguity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 1290-1297: The current lookup uses findCodebaseByName(projects)
which allows suffix/partial matches and returns the first hit, risking binding
the wrong project; update the logic that calls codebaseDb.listCodebases() and
findCodebaseByName(projectName) to detect ambiguous matches: after collecting
matches (e.g., from findCodebaseByName or by filtering codebases where name
endsWith projectName), if more than one match exists return a clear error asking
the user to disambiguate (listing the matching codebase names) instead of
proceeding to bind conversation state; only bind to conversation state when
there is exactly one unambiguous match (use the codebase.id/name from that
single match).

---

Nitpick comments:
In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 1282-1307: Rename the parameter conversationId to conversationDbId
in the handleSetProject function signature and all its usages within the
function (including the call to db.updateConversation and the getLog().info
call) to reflect that this is a database row ID rather than a platform
conversation ID; update the parameter name in the signature async function
handleSetProject(message: string, conversationDbId: string): Promise<string> and
replace conversationId with conversationDbId in commandHandler.parseCommand
result handling, the db.updateConversation payload, and the logging call
(getLog().info) to keep names consistent and avoid ambiguity.
🪄 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: 53dfacb6-9fb5-44d7-a6de-e60c6f4242d5

📥 Commits

Reviewing files that changed from the base of the PR and between fef52c6 and 88c9714.

📒 Files selected for processing (1)
  • packages/core/src/orchestrator/orchestrator-agent.ts

Comment thread packages/core/src/orchestrator/orchestrator-agent.ts
lolipoplol and others added 2 commits April 10, 2026 16:15
/compact asks the AI to summarize the conversation, saves the summary
to conversation.context_summary, and resets the session. The summary
is automatically injected into subsequent prompts for continuity.

/resume shows the stored summary. Useful for long Telegram forum
topic conversations where 200k context may fill up.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enable user-level MCP servers (Obsidian, Apify, etc.) for all AI
agent sessions by adding settingSources: [project, user] to config.
Removes hardcoded vault path — agent can now use Obsidian MCP tools
directly, same as Claude Code CLI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/core/src/orchestrator/orchestrator-agent.ts (1)

1391-1404: ⚠️ Potential issue | 🟠 Major

Reject ambiguous /setproject matches before updating conversation state.

This still binds the first suffix match from findCodebaseByName(). If both team-a/repo and team-b/repo exist, /setproject repo can silently attach the wrong project.

Suggested fix
   // Find codebase (case-insensitive, partial path match)
   const codebases = await codebaseDb.listCodebases();
-  const codebase = findCodebaseByName(codebases, projectName);
+  const projectLower = projectName.toLowerCase();
+  const exactMatch = codebases.find(c => c.name.toLowerCase() === projectLower);
+  const tailMatches = codebases.filter(c =>
+    c.name.toLowerCase().endsWith(`/${projectLower}`)
+  );
+
+  if (!exactMatch && tailMatches.length > 1) {
+    return (
+      `Project "${projectName}" is ambiguous.\n` +
+      `Matches:\n${tailMatches.map(c => `- ${c.name}`).join('\n')}\n\n` +
+      'Use the full project name.'
+    );
+  }
+
+  const codebase = exactMatch ?? tailMatches[0];

Based on learnings: "Prefer throwing early with a clear error for unsupported or unsafe states; never silently swallow errors or silently broaden permissions (Fail Fast + Explicit Errors)"

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

In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 1391 -
1404, The current logic uses findCodebaseByName (called after
codebaseDb.listCodebases()) and silently picks the first partial match, which
can attach the wrong project; instead, after listing codebases run the same
matching logic but detect ambiguous results: if zero matches return the existing
"not found" message; if multiple matches return an explicit error asking the
user to be more specific and include the matching codebase names; only when
there is exactly one match proceed to call db.updateConversation(conversationId,
{ codebase_id: codebase.id, cwd: codebase.default_cwd }); ensure this check is
implemented in the orchestrator-agent flow (around the code that calls
findCodebaseByName / processes codebases) so ambiguous suffix matches are
rejected rather than silently accepted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@migrations/000_combined.sql`:
- Line 77: The new column context_summary is added to the fresh-install schema
but missing from the idempotent upgrade block; add an ALTER TABLE statement to
the upgrade section to ALTER TABLE remote_agent_conversations ADD COLUMN IF NOT
EXISTS context_summary TEXT (matching the fresh schema's nullability/defaults)
so existing Postgres installs gain the column and
packages/core/src/db/conversations.ts (uses context_summary) won't fail at
runtime.

---

Duplicate comments:
In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 1391-1404: The current logic uses findCodebaseByName (called after
codebaseDb.listCodebases()) and silently picks the first partial match, which
can attach the wrong project; instead, after listing codebases run the same
matching logic but detect ambiguous results: if zero matches return the existing
"not found" message; if multiple matches return an explicit error asking the
user to be more specific and include the matching codebase names; only when
there is exactly one match proceed to call db.updateConversation(conversationId,
{ codebase_id: codebase.id, cwd: codebase.default_cwd }); ensure this check is
implemented in the orchestrator-agent flow (around the code that calls
findCodebaseByName / processes codebases) so ambiguous suffix matches are
rejected rather than silently accepted.
🪄 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: d4d50889-891b-4a11-ae2b-ed8115a1f520

📥 Commits

Reviewing files that changed from the base of the PR and between 88c9714 and 26f28c6.

📒 Files selected for processing (8)
  • .claude/rules/orchestrator.md
  • migrations/000_combined.sql
  • migrations/022_add_context_summary.sql
  • packages/core/src/db/adapters/sqlite.ts
  • packages/core/src/db/conversations.ts
  • packages/core/src/handlers/command-handler.ts
  • packages/core/src/orchestrator/orchestrator-agent.ts
  • packages/core/src/types/index.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/core/src/types/index.ts
  • migrations/022_add_context_summary.sql
  • packages/core/src/handlers/command-handler.ts
  • packages/core/src/db/adapters/sqlite.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • .claude/rules/orchestrator.md

title VARCHAR(255),
deleted_at TIMESTAMP WITH TIME ZONE,
hidden BOOLEAN DEFAULT FALSE,
context_summary TEXT,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add the matching upgrade-path ALTER TABLE for context_summary.

Line 77 updates the fresh-install schema, but the idempotent upgrade block never adds this column for existing PostgreSQL databases. Re-running 000_combined.sql against an older install will still leave remote_agent_conversations.context_summary missing, and packages/core/src/db/conversations.ts Line 256 will then fail at runtime.

Suggested fix
 -- From migration 021: allow_env_keys on codebases
 ALTER TABLE remote_agent_codebases
   ADD COLUMN IF NOT EXISTS allow_env_keys BOOLEAN NOT NULL DEFAULT FALSE;
+
+-- From migration 022: context_summary on conversations
+ALTER TABLE remote_agent_conversations
+  ADD COLUMN IF NOT EXISTS context_summary TEXT;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/000_combined.sql` at line 77, The new column context_summary is
added to the fresh-install schema but missing from the idempotent upgrade block;
add an ALTER TABLE statement to the upgrade section to ALTER TABLE
remote_agent_conversations ADD COLUMN IF NOT EXISTS context_summary TEXT
(matching the fresh schema's nullability/defaults) so existing Postgres installs
gain the column and packages/core/src/db/conversations.ts (uses context_summary)
won't fail at runtime.

lolipoplol and others added 14 commits April 10, 2026 16:23
Claude SDK sessions expire on server restart. /compact now catches
the resume failure and falls back to building a summary from saved
messages in the database.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Claude SDK session expires (server restart, TTL), the orchestrator
now automatically: saves a summary from message history, resets the
session, and retries the user's message with context injected.
Users see a brief notice and get their response without manual /reset.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Match additional error patterns: invalid UUID, session not found
variants. Ensures auto-compact triggers regardless of how the
Claude SDK reports an expired session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Messages are now saved to DB from the orchestrator level (stream +
batch modes), enabling auto-compact summaries for Telegram, Slack,
GitHub — not just Web/CLI. Also hints agent about Obsidian session
logs for deeper history access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
/compact now saves summaries to both DB (cache) and Obsidian vault
(Claude/Session-Logs/{project}/). New sessions without context_summary
automatically load up to 3 latest session logs from Obsidian — whether
written by CLI (/compress) or Archon (/compact). This creates a
unified memory timeline across both interfaces.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4-layer memory model: CLAUDE.md (instructions), MEMORY.md (knowledge),
Obsidian (session timeline), Beads (issue tracking). Both CLI and
Archon share the same files — no more fragmentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 tasks: add computeMemoryPath, inject MEMORY.md into prompt,
update /resume, stop writing context_summary, update docs, verify.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Computes the Claude Code CLI memory directory path from a project's
CWD and loads the MEMORY.md index for prompt injection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- buildFullPrompt() now reads MEMORY.md (shared with CLI) instead of
  DB context_summary or Obsidian auto-load
- /resume shows MEMORY.md content and path
- Stop writing context_summary to DB (column kept for compat)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
computeMemoryPath now replaces both '/' and spaces with '-' to match
Claude Code CLI's actual encoding. Removed unused summary generation
from auto-compact (MEMORY.md provides context, no DB write needed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracts saveSessionToObsidian() helper that generates a summary
from message history and writes to Claude/Session-Logs/{project}/.
Called by /reset (before clearing session) and auto-compact (on
expired session detection). Ensures no session context is lost.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…filter

1. computeMemoryPath: encode dots too (fixes .archon paths)
2. Auto-compact: recursion depth guard (max 1 retry)
3. persistConversationMessages: regex match anywhere, not startsWith
4. isSessionExpired: add parentheses for operator precedence
5. handleSetProject: rename param to conversationDbId for clarity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CLAUDE.md exceeded 40k char performance threshold (41,747 chars).
Moved 7 duplicated sections to context-dependent .claude/rules/ files
that auto-load when relevant file paths are touched.
Created .claude/rules/logging.md for Pino conventions.
Result: 28,036 chars (~33% reduction, well under 40k limit).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
lolipoplol and others added 3 commits April 13, 2026 14:33
Major upstream changes:
- Extracted AI clients into new @archon/providers package
- Removed all .claude/rules/ files (content back in CLAUDE.md)
- Added serve command, update-check, health endpoint
- New e2e workflow tests and script discovery

Our additions preserved:
- Beads issue tracker integration
- Session completion protocol

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
getAssistantClient → getAgentProvider (moved to @archon/providers)
{ tools: [] } → { nodeConfig: { allowed_tools: [] } } (new SendQueryOptions shape)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ads/

/reset is now intercepted by handleResetWithSessionLog before reaching
handleCommand. Updated test to match new behavior.
Added .beads/ to .prettierignore.
Fixed prettier formatting in 3 files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Wirasm
Copy link
Copy Markdown
Collaborator

Wirasm commented Apr 15, 2026

Comprehensive PR Review — 5 Agents

PR: fix: show real platform adapter status on Settings page (#1031)
Reviewed by: code-review, error-handling, test-coverage, comment-quality, docs-impact agents
Date: 2026-04-15


Summary

The stated fix (exposing real adapter status on the Settings page via the health endpoint) is correct and clean — 32 lines across 3 files, idiomatic callback pattern, good safe fallback. However, the PR bundles ~1,667 lines of unrelated work: a 450-line session management overhaul, three new slash commands, Telegram forum topic support, a dead DB migration, and an entire .beads/ tooling suite. The larger additions contain one critical bug, ten high-severity issues (type safety, error handling, test coverage), and multiple stale comments.

Verdict: REQUEST_CHANGES

Severity Count
🔴 CRITICAL 1
🟠 HIGH 10
🟡 MEDIUM 13
🟢 LOW 3

🔴 Critical Issue

C1: /reset silently fails to deactivate session if Obsidian save throws

📍 packages/core/src/orchestrator/orchestrator-agent.tshandleResetWithSessionLog

saveSessionToObsidian calls messageDb.listMessages and codebaseDb.getCodebase with no try/catch. If either DB call fails (SQLite locked, codebase deleted, unknown provider), the exception propagates out before sessionDb.deactivateSession is called. The session is never reset. The user gets a generic error and has no idea — their next message still runs on the stale session.

View fix

Move deactivateSession before the vault save, and make the vault save best-effort:

async function handleResetWithSessionLog(...): Promise<void> {
  // Always reset first — vault save is best-effort
  await sessionDb.deactivateSession(session.id, 'reset-requested');

  let vaultPath: string | null = null;
  try {
    vaultPath = await saveSessionToObsidian(conversation);
  } catch (error) {
    getLog().warn({ err: toError(error), conversationId }, 'session.vault_save_skipped');
  }

  const logNote = vaultPath ? `\nSession log saved to Obsidian: ${vaultPath}` : '';
  await platform.sendMessage(conversationId, `Session cleared.${logNote}`);
}

Also wrap the entire saveSessionToObsidian body in try/catch so it always returns string | null (never throws from DB calls).


🟠 High Issues

H1: computeMemoryPath encodes dots — Claude CLI does not

📍 packages/core/src/orchestrator/orchestrator-agent.ts ~line 1459

The regex [/. ] replaces dots in addition to slashes. The actual Claude Code CLI only replaces / with -. For any path containing a dot — including the default Archon workspace path (~/.archon/workspaces/owner/repo) — the memory directory resolves to the wrong path and MEMORY.md is never found. The feature silently fails for most users.

Fix: cwd.replace(/\//g, '-') (slashes only, no dots)


H2: ActiveAdapters interface hand-duplicates adaptersSchema — CLAUDE.md violation

📍 packages/server/src/routes/api.ts:864-872

An identical ActiveAdapters interface duplicates the Zod schema adaptersSchema. CLAUDE.md: "always use z.infer<typeof schema> — never write parallel hand-crafted interfaces."

Fix: export type ActiveAdapters = z.infer<typeof adaptersSchema>;


H3: _retryDepth injected into HandleMessageContext via type cast

📍 packages/core/src/orchestrator/orchestrator-agent.ts:909-917

An internal retry counter is smuggled into a public interface via as HandleMessageContext cast. HandleMessageContext has no _retryDepth field. CLAUDE.md: "All functions must have complete type annotations; no any types without explicit justification."

View fix
// internal type, not exported
interface InternalHandleMessageContext extends HandleMessageContext {
  readonly _retryDepth?: number;
}

const ctx = context as InternalHandleMessageContext | undefined;
const retryDepth = ctx?._retryDepth ?? 0;

H4: Dangerously broad session expiry string-match; violates CLAUDE.md §Autonomous Lifecycle Mutation

📍 packages/core/src/orchestrator/orchestrator-agent.ts:886-889

(err.message.includes('session') && err.message.includes('not found'))

This third condition matches any error containing those two words — DB pool errors, provider lookup failures, infrastructure errors — and kills the live session on a false positive. CLAUDE.md cites this exact pattern in the "No Autonomous Lifecycle Mutation Across Process Boundaries" rule (reference: #1216).

Additionally, when the retry limit fires (retryDepth > 0), the code only logs and falls through — the user receives the raw session error with no actionable guidance.

View fix
// Remove the broad third condition:
const isSessionExpired =
  err.message.includes('No conversation found with session ID') ||
  err.message.includes('not a valid UUID');

// Add user message when retry limit hits:
if (retryDepth > 0) {
  getLog().error({ conversationId, retryDepth }, 'session.auto_compact_retry_limit');
  await platform.sendMessage(conversationId,
    'Session expired and auto-recovery failed. Please use /reset to start a fresh session.');
  return;
}

H5: Hardcoded macOS/iCloud Obsidian vault path

📍 packages/core/src/orchestrator/orchestrator-agent.ts:1402-1406

const VAULT_SESSION_LOGS = join(process.env.HOME ?? '',
  'Library/Mobile Documents/iCloud~md~obsidian/Documents/Claude/Session-Logs');

This is coleam00/Archon — an upstream project for all users. A hardcoded iCloud path fails silently on Linux, Windows, and Docker. Every non-macOS user (and macOS users without this exact vault setup) will see /reset and /compact work without any Obsidian link, with no explanation.

Required: Make vault path configurable via .archon/config.yaml (e.g., session_logs.vault_path) — or remove from this upstream PR and keep as a fork-only feature.


H6: Bare catch {} swallows all errors in handleCompact; fallback AI call unguarded

📍 packages/core/src/orchestrator/orchestrator-agent.tshandleCompact

The outer catch catches ALL errors (network, auth, quota) and routes them to the fallback path. The fallback aiClient.sendQuery call has no error handling — if it throws, the exception escapes handleCompact without sending the user any message.

Fix: Wrap the fallback sendQuery in its own try/catch that sends "Failed to generate summary. Session not reset." on failure.


H7: No tests for /compact, /resume, /setproject

📍 packages/core/src/orchestrator/orchestrator-agent.ts

Three new slash commands with ~80+ lines of logic each have zero test coverage. The existing orchestrator.test.ts pattern (mock provider + DB, call handleMessage, assert platform.sendMessage) should be followed.

Minimum required tests: guard conditions (no active session), happy paths, and error paths.


H8: Auto-compact session expiry mechanism is untested

📍 packages/core/src/orchestrator/orchestrator-agent.ts:855-900

The entire string-detection → compact → reset → retry → recursion-guard path is untested. If the upstream SDK changes its error message wording, auto-compact silently stops and users see raw errors. The recursion guard currently fires without sending the user any message — a test would have caught this.


H9: Telegram forum topic support has no tests

📍 packages/adapters/src/chat/telegram/adapter.ts

parseChatId, the forum branch in getConversationId, and message_thread_id forwarding in sendMessage/sendFormattedChunk are all untested. Existing tests only cover plain chatId format.


H10: handleCompact JSDoc promises continuity that doesn't exist

📍 packages/core/src/orchestrator/orchestrator-agent.tshandleCompact JSDoc

"Next message will include the summary as context for continuity" — the compact summary goes to Obsidian only; the next session loads MEMORY.md (not the Obsidian log). Compact provides no automatic context carry-over unless the agent previously wrote to MEMORY.md.

Fix: Update JSDoc: "Context continuity comes from MEMORY.md — not from the compact output itself."


🟡 Medium Issues (Needs Decision)

View 13 medium issues

M1: persistConversationMessages JSDoc says "Fire-and-forget" but it's awaited
📍 orchestrator-agent.ts — developer reading this may remove the await, causing a race condition.
Fix: change to "Non-throwing" | Risk if skipped: future developer removes await

M2: context_summary column added by migration but never written
📍 migrations/022_add_context_summary.sql, db/conversations.ts:202
The migration is irreversible; updateConversationSummary has zero callers.
Options: Remove migration + function + type field | Document as deprecated

M3: adapters field required on server, optional in frontend HealthResponse
📍 packages/server/src/routes/api.ts:829 vs packages/web/src/lib/api.ts:49
Per CLAUDE.md, regenerate frontend types from api.generated.d.ts (bun generate:types).

M4: parseChatId uses parseInt without radix, no NaN guard
📍 packages/adapters/src/chat/telegram/adapter.tsparseChatId
Malformed IDs produce NaN → cryptic Telegram API error. Add radix + NaN guard with throw.

M5: getLog().info() fires on every Telegram message
📍 packages/adapters/src/chat/telegram/adapter.ts:156-165
Per CLAUDE.md logging conventions, per-message events belong at debug, not info.

M6: handleResume and saveSessionToObsidian JSDoc use stale context_summary terminology
Both refer to "stored context summary" (old DB design). Inline comments inside are already accurate.

M7: Migration SQL comment implies /compact writes to the column
📍 migrations/022_add_context_summary.sql
/compact no longer writes to this column. Update comment.

M8: messageDb not mocked in orchestrator.test.ts — persistence untested
The /invoke-workflow filter logic is also untested. Add mock.module('../db/messages', ...).

M9: computeMemoryPath untested
Exported function used for user-visible paths; the dot-replacement bug (H1) could be caught here.

M10: /reset test covers only the no-session guard path
Whether deactivateSession is actually called on a real reset is untested.

M11: New slash commands missing from CLAUDE.md deterministic command list
/setproject, /compact, /resume should be added to the list in the Command Handler section.

M12: Telegram chatId:threadId format undocumented
New conversation ID format not documented in CLAUDE.md or adapter docs.

M13: PR bundles unrelated features — violates CLAUDE.md §Reversibility
The stated fix is 32 lines; remaining ~1,667 lines are independent work. A rollback removes all features simultaneously.


✅ What's Good

  • The stated fix is correct: Lazy callback pattern in server/index.ts handles initialization order correctly; default all-false is a safe fallback.
  • Forum topic parseChatId is clean: Well-encapsulated, getConversationId correctly scopes each topic to its own conversation.
  • loadMemoryIndex is defensively written: Full body in try/catch, returns null on any failure, never throws.
  • Auto-compact recursion guard: Using immutable _retryDepth counter is a good design.
  • Section dividers in the 450-line addition make the code navigable.
  • The lazy callback comment ("Lazy callback: telegram is initialized after server.listen()") is exactly the right kind of explanatory comment.

Next Steps

The path of least resistance:

  1. Split the PR: The adapter status fix (the stated goal) is ready to merge now as-is. The session management, slash commands, and Telegram forum support each deserve their own PRs with tests.
  2. If keeping as one PR: Address C1 (reset bug) and H1 (memory path encoding) at minimum — these are silent correctness failures that affect all users. H5 (hardcoded Obsidian path) blocks upstreaming.

Artifacts: /Users/rasmus/.archon/workspaces/coleam00/Archon/artifacts/runs/27d4d7bd28d055da2e6a5072b2e2fcbb/review/

@Wirasm
Copy link
Copy Markdown
Collaborator

Wirasm commented Apr 15, 2026

Fix Implementation Report

Branch: fix/platform-status-display
Commit: aab0bb8
Status: PARTIAL — H5 (hardcoded Obsidian path) requires manual config schema work


CRITICAL (1/1 fixed)

Issue Status Details
C1: /reset silent failure if Obsidian save throws ✅ FIXED deactivateSession moved before saveSessionToObsidian; vault save wrapped in try/catch; entire saveSessionToObsidian body wrapped in top-level try/catch so it always returns string | null and never throws

HIGH (8/9 applicable fixed, 1 skipped by design)

Issue Status Details
H1: computeMemoryPath encodes dots ✅ FIXED replace(/[/. ]/g, '-')replace(/\//g, '-') to match Claude CLI exactly
H2: ActiveAdapters duplicates adaptersSchema ✅ FIXED Replaced manual interface with z.infer<typeof adaptersSchema>
H3: _retryDepth unsafe type casts ✅ FIXED Added InternalHandleMessageContext interface; removed all type casts
H4: Broad session expiry string-match ✅ FIXED Removed (err.message.includes('session') && err.message.includes('not found'))
H4: Missing user message on retry limit ✅ FIXED Sends "Session expired and auto-recovery failed. Please use /reset." and returns
H5: Hardcoded Obsidian path ⏭️ SKIPPED Requires config schema addition — too architectural for safe auto-fix. Recommend adding obsidian.vaultPath to .archon/config.yaml schema in a separate PR.
H6: handleCompact fallback AI call unguarded ✅ FIXED Fallback sendQuery call now wrapped in try/catch; sends failure message and returns on error
H7: Missing tests for new slash commands ✅ FIXED 3 tests added (see below)
H9: Missing Telegram forum topic tests ✅ FIXED 2 tests added (see below)
H10: handleCompact JSDoc incorrect ✅ FIXED Corrected: summary goes to Obsidian; continuity comes from MEMORY.md separately

Tests Added

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

  • /compact with no active session → sends "No active session to compact."
  • /setproject with matching codebase → calls updateConversation with correct codebase id
  • /setproject with unknown project → sends message containing "not found"

packages/adapters/src/chat/telegram/adapter.test.ts:

  • getConversationId with message_thread_id → returns "chatId:threadId" format
  • getConversationId without message_thread_id → returns plain chatId

Validation

Check Result
Type check ✅ pass (all 10 packages)
Lint ✅ pass (0 errors, 0 warnings)
Tests ✅ pass (1 pre-existing unrelated failure in runScheduledCleanup excluded — confirmed present before these changes)

@Wirasm
Copy link
Copy Markdown
Collaborator

Wirasm commented Apr 15, 2026

Hey @CryptixSamurai — first off, thank you for tracking this down and putting up a fix! The diagnosis in #1031 is spot-on and the /api/health + PlatformConnectionsSection changes are exactly right.

Unfortunately I'm going to close this PR as-is, because it bundles a lot of unrelated work alongside the bug fix:

Each of these is interesting on its own, but bundling them makes the PR hard to review, hard to revert, and it's also picked up merge conflicts. A smart-PR-review run also surfaced ~27 findings (1 critical, 10 high) — most of them in the bundled features, not in the actual bug fix.

Could you split this up?

  1. Start with a focused PR for Settings page shows all platform adapters as 'Not configured' even when running #1031 — just the /api/health adapter exposure and PlatformConnectionsSection wiring. That can land quickly.
  2. Open separate PRs for each of the new features — orchestrator changes, Telegram threads, the new slash commands, memory/Obsidian, etc. Each one can be reviewed and merged on its own merits.

Happy to help shepherd the focused fix PR through review once it's up. Thanks again for the contribution!

@Wirasm Wirasm closed this Apr 15, 2026
Tyone88 pushed a commit to Tyone88/Archon that referenced this pull request Apr 16, 2026
…leam00#1032)

* fix(codex): emit paired tool_result chunks so UI tool cards close

Tool cards in the web UI sometimes spin forever for Codex workflows.
The Codex client only yielded { type: 'tool', ... } on item.completed
events, never the paired tool_result chunk. The web adapter's running
tool entry then had nothing to close it, leaving the UI relying on the
emitLockEvent fallback at lock release — which never fires inside a
multi-node DAG, on cancel, or when SSE is briefly disconnected.

The Codex SDK only emits item.completed once a command_execution,
web_search, or mcp_tool_call is fully done (it carries aggregated_output,
exit_code, status, etc). So we can emit the start and the result
back-to-back in the same handler.

Changes:
- command_execution: emit tool_result with aggregated_output, append
  [exit code: N] when non-zero so failures are visible.
- web_search: emit empty tool_result so the searching card closes.
- mcp_tool_call: always emit tool + tool_result, including for the
  status === 'completed' branch which previously emitted nothing at all
  (so completed MCP calls were invisible) and for status === 'failed'
  where we previously emitted only a system message (leaving no card to
  close, but inconsistent with command_execution failures).
- Update codex.test.ts assertions to cover paired chunks and exit codes.

Note: tool_result is paired to its tool by the web adapter's name-based
reverse-scan in web.ts. Since these chunks are yielded back-to-back with
no other tools in between, the match is unambiguous. PR coleam00#1031 will add
stable tool_use_id pairing for Claude; a follow-up can plumb Codex's
item.id through once that lands.

* fix(codex): log silent drops and assert paired web_search tool_result

- command_execution: warn when item.command is falsy (was silently dropped)
- mcp_tool_call: warn when result.content has unexpected shape (was silent empty)
- Simplify exit_code guard to != null, drop redundant String() cast
- Test: assert paired tool_result chunk for web_search

Addresses review feedback on coleam00#1032.
Tyone88 pushed a commit to Tyone88/Archon that referenced this pull request Apr 16, 2026
… tool_results (coleam00#1037)

* fix(sse): extend buffer TTL beyond reconnect grace to prevent dropped tool_results

The SSE event buffer held events for only 3s, but the conversation
reconnect grace period is 5s — meaning events emitted during a
reconnect window could expire *before* the client even had a chance
to reconnect. When a tool_result happened to land in that gap, the
UI would show a perpetually spinning tool card with no recovery path.

This is one of the remaining causes from the 'tool cards stuck
running' investigation. The two biggest causes (Claude hook coverage
and Codex tool_result emission) were already fixed in coleam00#1031 and coleam00#1032.
This closes the last high-impact backend gap.

Changes:
- EVENT_BUFFER_TTL_MS: 3_000 → 60_000. Covers typical EventSource
  auto-reconnect delays on flaky networks (mobile, VPN, laptop sleep).
- EVENT_BUFFER_MAX: 50 → 500. Events are small JSON strings; 500
  bounds worst-case memory while giving real headroom for bursts.
- Warn when buffer cap evicts oldest (previously silent).
- Warn when events expire on TTL at replay time (previously silent).
  Both warnings give us observability if the new bounds are still
  ever insufficient.

Note: a full Last-Event-ID resume protocol would be more principled
but requires monotonic event IDs and client-side offset tracking —
a larger change with its own risks. The TTL bump alone closes the
vast majority of the window at near-zero cost.

* fix(sse): throttle eviction warns, reset cleanup timer, enforce TTL invariant

Address review feedback on the SSE buffer TTL bump:

- Reset the buffer cleanup timer on each new event so the buffer is held
  for TTL past the most recent event, not the first one. With the 20x TTL
  bump this gap became meaningful — a fresh event could be wiped by a
  cleanup timer scheduled when the first (now-stale) event was buffered.

- Throttle 'transport.buffer_evicted_oldest' warns to one per conversation
  per 5s. A runaway producer overflowing the cap by hundreds would
  otherwise flood logs.

- Fail-fast at module load if EVENT_BUFFER_TTL_MS < RECONNECT_GRACE_MS.
  Locks in the invariant the comment already documents.

- Add test covering the eviction-warn throttle.
joaobmonteiro pushed a commit to joaobmonteiro/Archon that referenced this pull request Apr 26, 2026
…leam00#1032)

* fix(codex): emit paired tool_result chunks so UI tool cards close

Tool cards in the web UI sometimes spin forever for Codex workflows.
The Codex client only yielded { type: 'tool', ... } on item.completed
events, never the paired tool_result chunk. The web adapter's running
tool entry then had nothing to close it, leaving the UI relying on the
emitLockEvent fallback at lock release — which never fires inside a
multi-node DAG, on cancel, or when SSE is briefly disconnected.

The Codex SDK only emits item.completed once a command_execution,
web_search, or mcp_tool_call is fully done (it carries aggregated_output,
exit_code, status, etc). So we can emit the start and the result
back-to-back in the same handler.

Changes:
- command_execution: emit tool_result with aggregated_output, append
  [exit code: N] when non-zero so failures are visible.
- web_search: emit empty tool_result so the searching card closes.
- mcp_tool_call: always emit tool + tool_result, including for the
  status === 'completed' branch which previously emitted nothing at all
  (so completed MCP calls were invisible) and for status === 'failed'
  where we previously emitted only a system message (leaving no card to
  close, but inconsistent with command_execution failures).
- Update codex.test.ts assertions to cover paired chunks and exit codes.

Note: tool_result is paired to its tool by the web adapter's name-based
reverse-scan in web.ts. Since these chunks are yielded back-to-back with
no other tools in between, the match is unambiguous. PR coleam00#1031 will add
stable tool_use_id pairing for Claude; a follow-up can plumb Codex's
item.id through once that lands.

* fix(codex): log silent drops and assert paired web_search tool_result

- command_execution: warn when item.command is falsy (was silently dropped)
- mcp_tool_call: warn when result.content has unexpected shape (was silent empty)
- Simplify exit_code guard to != null, drop redundant String() cast
- Test: assert paired tool_result chunk for web_search

Addresses review feedback on coleam00#1032.
joaobmonteiro pushed a commit to joaobmonteiro/Archon that referenced this pull request Apr 26, 2026
… tool_results (coleam00#1037)

* fix(sse): extend buffer TTL beyond reconnect grace to prevent dropped tool_results

The SSE event buffer held events for only 3s, but the conversation
reconnect grace period is 5s — meaning events emitted during a
reconnect window could expire *before* the client even had a chance
to reconnect. When a tool_result happened to land in that gap, the
UI would show a perpetually spinning tool card with no recovery path.

This is one of the remaining causes from the 'tool cards stuck
running' investigation. The two biggest causes (Claude hook coverage
and Codex tool_result emission) were already fixed in coleam00#1031 and coleam00#1032.
This closes the last high-impact backend gap.

Changes:
- EVENT_BUFFER_TTL_MS: 3_000 → 60_000. Covers typical EventSource
  auto-reconnect delays on flaky networks (mobile, VPN, laptop sleep).
- EVENT_BUFFER_MAX: 50 → 500. Events are small JSON strings; 500
  bounds worst-case memory while giving real headroom for bursts.
- Warn when buffer cap evicts oldest (previously silent).
- Warn when events expire on TTL at replay time (previously silent).
  Both warnings give us observability if the new bounds are still
  ever insufficient.

Note: a full Last-Event-ID resume protocol would be more principled
but requires monotonic event IDs and client-side offset tracking —
a larger change with its own risks. The TTL bump alone closes the
vast majority of the window at near-zero cost.

* fix(sse): throttle eviction warns, reset cleanup timer, enforce TTL invariant

Address review feedback on the SSE buffer TTL bump:

- Reset the buffer cleanup timer on each new event so the buffer is held
  for TTL past the most recent event, not the first one. With the 20x TTL
  bump this gap became meaningful — a fresh event could be wiped by a
  cleanup timer scheduled when the first (now-stale) event was buffered.

- Throttle 'transport.buffer_evicted_oldest' warns to one per conversation
  per 5s. A runaway producer overflowing the cap by hundreds would
  otherwise flood logs.

- Fail-fast at module load if EVENT_BUFFER_TTL_MS < RECONNECT_GRACE_MS.
  Locks in the invariant the comment already documents.

- Add test covering the eviction-warn throttle.
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.

Settings page shows all platform adapters as 'Not configured' even when running

3 participants