Skip to content

fix(cli): bridge connects in a fresh terminal even when the app is down - #55

Merged
runyourempire merged 3 commits into
mainfrom
worktree-fix-bridge-cold-start
Jul 13, 2026
Merged

fix(cli): bridge connects in a fresh terminal even when the app is down#55
runyourempire merged 3 commits into
mainfrom
worktree-fix-bridge-cold-start

Conversation

@runyourempire

Copy link
Copy Markdown
Collaborator

Problem

Opening a fresh Claude Code terminal in a Victauri-consuming app (4DA) showed:

Failed to reconnect to victauri: MCP server "victauri" connection timed out after 30000ms

Root cause is architectural, not the app. Victauri's MCP server is embedded inside the Tauri process, so it only exists while the app runs. victauri bridge discovered the backend before answering the MCP initialize handshake (worse under --wait, which blocked ~30s), so the host aborts at its 30s connection ceiling — meaning every fresh terminal opened before the app was started failed to connect, and the agent silently loses Victauri (falling back to worse tooling or flying blind).

Fix (all in crates/victauri-cli/src/bridge.rs; public API unchanged → semver-clean in ^0.8)

  • Local handshake (keystone): answer initialize/tools/list/ping/*list locally, so the MCP server always shows connected with its full tool surface whether or not an app is running. Only tool calls need a live backend; when down they return an actionable "backend not reachable — start the app (npm run tauri dev)" error instead of hanging.
  • Baked fallback tool list: while the app is down, tools/list serves all 35 tools (names + descriptions extracted from the plugin's #[tool] annotations → tools_fallback.json, include_str!-embedded).
  • Auto-go-live, no reconnect: a background availability poller emits notifications/tools/list_changed (+ resources/list_changed) on the down→up transition, so the client refreshes from the fallback to the live, version-accurate list automatically.
  • Cheap discovery: batch liveness into ONE tasklist/ps call per scan (not one spawn per stale dir), filter dead-PID entries before any /health probe, and bound the probe connect to 600ms so a closed/filtered stale port can't stall a scan. Cold-start scan with ~25 stale dirs now <1s (was ~9s, then a 20s hang mid-iteration — both fixed).
  • victauri init drops the now-vestigial --wait from generated .mcp.json (still accepted for back-compat); generated CLAUDE.md guidance updated to say the server connects even before the app is up.

Verification (against the compiled binary)

  • New e2e bridge_cold_start_serves_handshake_then_goes_live_when_app_appears: instant handshake with no app → 35-tool fallback → actionable down-call error → auto-emitted list_changed when a mock backend appears → live tool list + working tool call, no reconnect.
  • Existing restart-recovery e2e still passes.
  • Manual drive of the real binary (victauri bridge, app down, 25 stale dirs): initialize near-instant, full sequence <1s (was 30 000ms timeout).

Gate green: cargo fmt --all --check, cargo clippy --workspace --all-targets -D warnings, cargo build --workspace, cargo test -p victauri-cli (46 unit + 2 e2e).

🤖 Generated with Claude Code

audit and others added 3 commits July 13, 2026 03:12
Victauri's MCP server is embedded inside the Tauri app, so it only exists
while the app runs. `victauri bridge` discovered the backend BEFORE answering
the MCP `initialize` handshake (blocking ~30s under `--wait`), so the host
aborted at its 30s connection ceiling — every fresh terminal opened before the
app started reported:

  Failed to reconnect to victauri: MCP server "victauri" connection timed out
  after 30000ms

Fix (all in crates/victauri-cli/src/bridge.rs; public API unchanged, semver-clean):

- Answer `initialize`/`tools/list`/`ping`/`*list` LOCALLY so the server always
  shows connected with its full tool surface, app or no app. Only tool CALLS
  need a live backend; when down they return an actionable "start the app"
  error instead of hanging.
- Serve a baked 35-tool fallback list (names+descriptions extracted from the
  plugin's #[tool] annotations -> tools_fallback.json) while the app is down.
- Background availability poller emits notifications/tools/list_changed on the
  down->up transition, so tools go live automatically with no /mcp reconnect.
- Make discovery cheap: batch liveness into ONE tasklist/ps call per scan
  (not one spawn per stale dir), filter dead-PID entries before any /health
  probe, and bound the probe connect to 600ms so a closed/filtered stale port
  can't stall a scan. Cold-start scan with ~25 stale dirs now <1s (was ~9s).
- `victauri init` drops the now-vestigial `--wait` from generated .mcp.json
  (still accepted for back-compat); CLAUDE.md guidance updated.

Verified end-to-end against the compiled binary: new e2e test proves instant
handshake with no app, the fallback list, an actionable down-call error, then
auto-emitted list_changed + live tools + a working call when a mock backend
appears — no reconnect. Existing restart-recovery e2e still passes.
Gate: fmt --all, clippy --workspace --all-targets -D warnings, build
--workspace, test -p victauri-cli (46 unit + 2 e2e) all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfH8iYXUYm5YruHescthzD
…liable

Live-4DA verification exposed an intermittency I introduced: `health_ok`
built a fresh reqwest client per call with a 1s total timeout, so the FIRST
(cold) /health probe against a running server under load could exceed it —
discovery then missed the live app on first contact and the bridge served the
fallback tool list / errored the first tool call (a retry then worked).

Fix: build ONE warm health client in a OnceLock and keep a tight CONNECT
timeout (1200ms — still fast-fails a closed/filtered stale port) with a
generous total timeout (3s, only relevant for a rare connected-but-
unresponsive reused port). Verified against live 4DA: 5/5 fresh cold-connects
now return the LIVE tool list (real schemas) + a working get_plugin_info call
on first contact (442-1069ms); `victauri check` is green (379 commands).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfH8iYXUYm5YruHescthzD
CI's Check & Lint upgraded to clippy 1.97.0, whose question_mark lint flags a
pre-existing manual `if let Some(i) = ... else { return None }` in
extract_fn_name. Rewrite as `let i = line.find("fn ")?;`. Verified with a
matching local toolchain: `cargo clippy --workspace --all-targets -D warnings`,
`cargo fmt --all --check`, and `cargo test -p victauri-cli` all green on 1.97.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfH8iYXUYm5YruHescthzD
@runyourempire
runyourempire merged commit c192083 into main Jul 13, 2026
21 checks passed
@runyourempire
runyourempire deleted the worktree-fix-bridge-cold-start branch July 13, 2026 02:25
runyourempire added a commit that referenced this pull request Jul 13, 2026
…58)

victauri-cli-only, semver-clean within ^0.8 (plugin + public Rust API
unchanged). Bundles: the bridge cold-start fix (#55 — connects in a fresh
terminal even with the app down), pre-emptive internal red-team hardening (#56),
external GPT audit remediation incl. a real token-leak fix + regression test,
and round-2 verification follow-ups (#57).

- scripts/bump-version.ps1 0.8.7 (workspace version, dep pins, action pin,
  CLAUDE.md refs, Cargo.lock)
- CHANGELOG [Unreleased] -> [0.8.7] - 2026-07-13
- MIGRATION.md v0.8.6 -> v0.8.7 (bridge connects in a fresh terminal; --wait
  no-op; per-forward trusted re-resolve)
- CLAUDE.md Current State -> v0.8.7

Gate green locally: fmt --all, clippy --workspace --all-targets -D warnings
(1.97), build --workspace, test -p victauri-cli (46 unit + 3 e2e). NOT published
(operator's crates.io gate).


Claude-Session: https://claude.ai/code/session_01KfH8iYXUYm5YruHescthzD

Co-authored-by: audit <audit@example.test>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant