Rhys Sullivanfmtc92c57d6 months agoHistory
86 lines · 2.8 KB · Markdown
RawDownload

Keep Errored Sources Visible

What happens today

config-sync (apps/local/src/server/config-sync.ts) reads executor.jsonc and calls the plugin's addSource / addSpec for each entry. If one fails (auth, network, unparseable spec), the error is logged and the rest keep loading:

[config-sync] Failed to load source "mcp_axiom_co": MCP discovery failed:
  Failed connecting to MCP server: Failed connecting via sse:
  SSE error: Non-200 status code (401)
[config-sync] 3/4 source(s) synced

The row never reaches the DB. executor.jsonc still has it, so next boot retries — idempotent, but from the UI's perspective the source has silently vanished.

The gap

The user sees 3 sources in the UI. They have no way to know a 4th existed, was tried, and failed. The only record of the failure is stderr. Reasonable people end up confused — "did it delete my source?"

What we want

The source stays visible after a failed add, marked as errored with a human-readable reason. The UI shows it with a warning badge; a retry button calls refresh or re-runs the config-sync entry for just that source.

Sketch

Schema — extend the source table:

source: {
  fields: {
    id: { type: "string", required: true },
    scope_id: { type: "string", required: true, index: true },
    // ...existing...
    status: { type: "string", required: true, defaultValue: "healthy" },  // "healthy" | "error"
    last_error: { type: "string" },        // message
    last_error_at: { type: "date" },       // for UI "last tried N minutes ago"
  },
}

config-sync — on failure, still write a row:

Effect.catchAll((e) => {
  const message = e instanceof Error ? e.message : String(e);
  return executor.sources.recordError({ source, message }).pipe(Effect.asVoid);
});

executor.sources.recordError is a new core method that writes a source row with status="error" and last_error. Plugin-specific data (spec JSON, endpoint URL, headers) is still pulled from the config entry, so the row is mostly populated — just no tools attached because discovery never ran.

UI — sources list badges errored entries, shows last_error on hover/click, offers "Retry" which calls executor.sources.refresh(id).

Interaction with removes

An errored source is still user-visible, so removeSource should work on it (user can delete the jsonc entry and the DB row together). Not a special case — current removeSource already works regardless of status.

Scope

Not for the scope-refactor PR. Separate change:

  • Schema migration adds two columns.
  • executor.sources.recordError on the core SDK.
  • Wire up in config-sync.
  • UI badge + retry button in the sources list.

Doable in a day. Worth doing because silent source dropping is the single most confusing UX we have today.