Skip to content

feat(preflight): pre-session connector preflight with per-slot-type on_degraded policy (connector-resilience Phase 1) - #187

Open
jordanrburger wants to merge 1 commit into
mainfrom
claude/recursing-bartik-ef22a1
Open

feat(preflight): pre-session connector preflight with per-slot-type on_degraded policy (connector-resilience Phase 1)#187
jordanrburger wants to merge 1 commit into
mainfrom
claude/recursing-bartik-ef22a1

Conversation

@jordanrburger

Copy link
Copy Markdown
Collaborator

Implements Phase 1 of the connector-resilience design (#181docs/superpowers/specs/2026-07-01-connector-resilience-design.md): the pre-session connector preflight (Layers 1–2). Phase 2 (gap records in .scout-state/gaps.jsonl + post-session reconciliation — gated on #121) and Phase 3 (/scout-backfill) are intentionally not included.

What's in

  • scoutctl connectors preflight --mode <slot-key> | --slot-type <type> (engine/scout/scripts/connector_preflight.py) — probes every connector critical for the slot type before the main session launches, at zero model-token cost:
    • MCP connectors parsed out of one claude mcp list invocation, matched verbatim by a new harness_server_name field on the connector roster (registry keys/display names can't be mechanically mapped to harness server names, per the spec).
    • Bash-probed connectors via a new preflight_command field (githubgh auth status), harness-independent.
    • A connector with neither field is simply not probed.
    • Degraded iff any critical connector is determinably down — no quorum/threshold knob, by design.
  • connector_policy block in scout-config.yaml (templates/scout-config.yaml.tmpl): on_degraded: skip | warn | run, global default + per-slot-type overrides. Shipped default is run — existing installs see zero behavior change until they opt in. Malformed config falls back with a stderr warning, never crashes the runner.
  • Runner integration in all three run-*.sh.tmpl, following the budget-check precedent: exit 3 = policy skip → runner logs it and exits 0 (orderly, like a budget skip); any other non-zero exit fails open — a preflight crash never masquerades as a deliberate skip. MODE is hoisted above the gate block in the dreaming/research runners (it was defined after them).
  • Warn seam: preflight writes .scout-cache/connector-degradation-pending.md naming the dark connectors; the session phases (phases/core/00-run-modes.md, phases/modes/kb-deep-work.md) consume it — prepend a degradation banner, record no "nothing found"-style negative signals for dark connectors, delete after consuming.
  • Skip alert: appends to .scout-logs/connector-alerts.log + best-effort Telegram (action_required), swallowing missing-secret/network failures.
  • Error handling per spec: claude mcp list failure/timeout/unparseable output → inconclusive, exit 4, runner fails open. A persisted inconclusive-streak counter (.scout-state/connector-preflight-state.json) fires the alert channel once the streak hits 3 — fail-open + glyph parsing means a routine CLI format change would otherwise silently disable the whole protection.
  • last_healthy_run[slot_type] stamped on healthy preflights (same state file) so Phase 2 gap records get accurate from windows from day one. Per spec, a degraded run proceeding under warn/run does not stamp it.

⚠️ Reviewable decision: recommended posture is warn, not skip, for briefing/consolidation

The spec's recommended posture sets briefing/consolidation to skip. This PR's commented-out recommended block in scout-config.yaml.tmpl suggests warn for them instead, deliberately: the claude mcp list probe has a known false-positive mode (anthropics/claude-code#44535), and until the Phase-2 reconciliation/gap-tracking safety net exists, a false-positive under skip silently drops runs with no recorded gap — exactly the "silent loss" this design exists to prevent. warn still runs the session, banners the degradation, and suppresses false negative signals. Once Phase 2 lands, flipping the recommendation back to skip is a one-line template change. (Note the shipped default is run either way; this only affects the suggested opt-in posture.) Happy to flip to skip if you'd rather match the spec text as written.

Scope notes / follow-ups

  • Persistent-degraded-streak alert (guards the stuck-skip trap from phantom/zombie connectors, [BUG] Connectors continue to appear in claude mcp list even after disconnected in the Claude.ai web UI anthropics/claude-code#48275): out of scope here; noted as a follow-up — the state file already gives it a natural home.
  • Un-probed seed connectors: mcp:fathom, mcp:claude-in-chrome, mcp:whatsapp-mcp ship without probe fields — they don't reliably appear in claude mcp list (browser extension / local bridge), and a wrong harness_server_name would read as permanently degraded. Users can wire them via the connectors.local.yaml overlay; the fields are overlay-able like the rest of the roster.
  • Known Phase-1 limitation (documented in the spec): a fail-open blind run goes unrecorded until Layer 1b ships in Phase 2.
  • connectors.snapshot.json is unaffected — the cross-repo projection only carries key/display_name/tier (verified with --check).

Testing

  • engine/tests/unit/test_connector_preflight.py — parser fixtures for all four status markers incl. plugin-scoped names (anonymized per CLAUDE.md), unparseable → inconclusive-not-degraded, policy resolution incl. malformed fallbacks, mode→slot-type resolution, degraded classification (any-critical-down; missing-from-list = down; unknowns fail open; bash probes), run() exit codes, warn pending file, skip alert, healthy stamp, streak alert.
  • engine/tests/unit/test_runner_preflight_templates.py — renders each runner template into a tmp vault with stubbed scoutctl/claude and runs it under bash: exit 3 → orderly skip (session never launches), exit ≠ 0/3 → fail open, MODE defined before the gate (verified these fail against the pre-change templates).
  • Full suite: 938 passed; ruff format --check, ruff check, mypy clean (same commands as CI, from engine/ with .venv/bin/*).
  • Live end-to-end on a real config (44 MCP servers): healthy path classifies correctly and stamps state; a forced-degraded warn run writes the pending file naming the dark connector.

🤖 Generated with Claude Code

…n_degraded policy (connector-resilience Phase 1)

Implements Layers 1-2 of the connector-resilience design (#181,
docs/superpowers/specs/2026-07-01-connector-resilience-design.md):

- scout.scripts.connector_preflight: probes every connector critical for
  the upcoming slot type before the session launches — MCP connectors via
  one `claude mcp list` invocation matched verbatim by a new
  harness_server_name field; CLI connectors via a preflight_command bash
  probe (gh auth status). Degraded iff any critical connector is
  determinably down (no quorum knob by design).
- connector_policy block in scout-config.yaml: on_degraded skip|warn|run,
  global default + per-slot-type overrides. Default `run` — no behavior
  change until users opt in.
- Runner integration in all three run-*.sh.tmpl following the budget-check
  precedent: exit 3 = orderly skip, any other non-zero = fail open. MODE
  hoisted above the gates in the dreaming/research runners.
- Warn seam: .scout-cache/connector-degradation-pending.md consumed by the
  session phases (banner + no negative signals for dark connectors).
- Error handling per spec: fail open on probe errors; inconclusive-streak
  counter alerts after 3 consecutive inconclusive probes; healthy runs
  stamp last_healthy_run per slot type for Phase 2 gap windows.

Phase 2 (gaps.jsonl + post-session reconciliation, gated on #121) and
Phase 3 (/scout-backfill) are intentionally not included.

Co-Authored-By: Claude Fable 5 <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