Skip to content

Missing credential: typed error naming the connector - #232

Merged
TheGreatAxios merged 5 commits into
mainfrom
cl-connect-popup
Aug 21, 2026
Merged

TheGreatAxios merged 5 commits into
mainfrom
cl-connect-popup

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • The owner's ask: when an agent needs a tool whose credential isn't connected, it must stop and give a one-click connect, not a dead-end error.
  • Investigation found the connect UI side is already fully built and reusable as-is: the connect-service chat block (packages/chat/src/blocks.ts), its view + container (packages/chat-ui/src/blocks/connect-service-block*.tsx), and createChatConnectServiceActions (apps/web/src/connect-service-actions.ts) already render a real "Connect X" button, round-trip through the existing OAuth/MCP-preset start-and-return routes, and land the user back where they were. Nothing needed to be added or duplicated there.
  • Two halts, now both wired:
    1. Launch-time (a credential unresolved before a run starts): packages/connections/src/credential-error.ts's MissingCredentialError wired into packages/folded-runs/src/launch.ts.
    2. Mid-turn (the owner's actual demo scenario -- an already-running agent's tool call finds no bound credential): closed in this update. ToolResult.detail (@intx/types/runtime) already exists for carrying structured, non-model-facing metadata off a tool result, but it was being dropped at two points: packages/folded-runs/src/agent-events.ts's toolDoneResult only read callId/content/isError off the sidecar event, and packages/chat/src/chat-orchestrator.ts's tool-done handler only forwarded content/isError into the tool-trace part. Both now carry detail through; when it parses (via new packages/connections/src/missing-credential-detail.ts's parseMissingCredentialDetail) as a missing-credential signal, the orchestrator appends the same connect-service block request_connection already posts for the agent-initiated path -- same block type, same ConnectServiceBlockContainer, same createChatConnectServiceActions round-trip -- so a live turn gets a real "Connect GitHub" button instead of a dead-end error. The connector is always named via CONNECTOR_REGISTRY's displayName, never its id.
  • GitHub's pull-request tools (packages/github-tools/src/pull-request-tools.ts) are wired end-to-end as the first mover: their existing NOT_CONNECTED result now carries the detail literally, with no new dependency -- a sandboxed tool package agrees with the reader on this wire shape by convention (a kind-discriminated literal), the same way toolDoneResult already narrows event.type without importing a shared constant.
  • Regression guard: a tool result carrying no detail, or a detail that isn't this shape, or an isError: false result, renders exactly as before -- covered by tests in chat-orchestrator.test.ts and agent-events.test.ts.
  • Not yet done: only GitHub's pull-request tools produce this detail. Extending the same one-line convention to the other tool packages' own NOT_CONNECTED-style results (granola-tools, linear-tools, reddit-tools, web-search-tools, mcp-tools -- and Jimmy's Giphy tool once it lands) is the same pattern, not a new design.

Verification

Per the owner's live directive (their machine OOM'd from concurrent full-repo checks across lanes), repo-root bun run check and repo-root lint were not run -- only package-scoped checks:

  • bun test on the touched files across packages/connections, packages/folded-runs, packages/chat, packages/github-tools -- 29 pass, 0 fail.
  • tsc --noEmit inside each of those four packages -- clean.
  • Repo-root lint/typecheck/full bun run check were not run -- left for CI.

Test plan

  • CI: full bun run check
  • MissingCredentialError("github") names displayName: "GitHub", falls back to the raw id for an unregistered connector
  • credentialDeliveryError: an "unresolved" failure becomes MissingCredentialError naming the connector; "ambiguous" stays generic
  • A tool result whose detail is { kind: "missing-credential", connectorId } renders the connect-service block naming the connector via the live turn's accumulated parts
  • A normal tool result, a failed tool result with no such detail, and a missing-credential detail on a non-error result are all unaffected (no block appended)
  • GitHub's NOT_CONNECTED result carries the detail; a resolved credential carries none

Red until MissingCredentialError and credentialDeliveryError exist:
today a missing credential collapses into a generic Error, losing which
connector needs to be connected.
buildCredentialDelivery already resolves which connector's binding is
unresolved (reason.binding.provider); the folded-run launch path used
to discard that identity into a generic Error string. It now throws
connections' MissingCredentialError, carrying the connector id and its
CONNECTOR_REGISTRY display name, so a launch halts on a missing
credential without guessing and names exactly what needs connecting.
Other buildCredentialDelivery failures (ambiguous/no_origin) stay
generic, since those are config faults, not something to connect.

The existing connect-service chat block, its container, and
createChatConnectServiceActions (apps/web) already render a working
one-click connect affordance for a named connector and land the user
back on success via the existing OAuth/MCP-preset return-path
machinery; wiring this new error into that render path — passing the
connector id from a mid-run tool failure through to the chat-orchestrator
tool-trace output — is the follow-up this unlocks.
Red until the wire detail exists and survives the sidecar-event ->
tool-trace path: a tool result whose detail names a missing connector
should render the existing connect-service block in the live turn; a
normal tool result (no such detail) must render exactly as before.
The launch-time halt (previous commit) only covers a credential
unresolved before a run starts. The scenario that actually fires
mid-conversation -- an already-running agent hitting a tool with no
bound credential -- produced a tool failure with no connect prompt,
because ToolResult.detail (the side channel @intx/types/runtime
already carries for exactly this) was dropped at two points: folded-runs'
toolDoneResult only read callId/content/isError off the sidecar event,
and chat-orchestrator's tool-done handler only forwarded content/isError
into the tool-trace part.

Both now carry detail through. When it parses as a missing-credential
signal (packages/connections' new parseMissingCredentialDetail), the
chat orchestrator appends the same connect-service block
request_connection already posts for the agent-initiated path -- same
block type, same ConnectServiceBlockContainer, same
createChatConnectServiceActions round-trip through OAuth/MCP-preset
connect-and-return -- so a live turn gets a real "Connect GitHub" button
instead of a dead-end error, naming the connector via
CONNECTOR_REGISTRY's display name rather than its id. A tool result
carrying no such detail renders exactly as before.

GitHub's pull-request tools are wired end to end as the first mover:
their existing NOT_CONNECTED result now carries the detail literally
(no new dependency -- a sandboxed tool package agrees with the reader
on this wire shape by convention, not by importing it).
@TheGreatAxios
TheGreatAxios merged commit 9059b32 into main Aug 21, 2026
2 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-connect-popup branch August 25, 2026 15:29
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