# 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: ```ts 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: ```ts 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.