Skip to content

fix: connector tools fail gracefully + operator allow/deny tool filter - #449

Open
Brad Huffman (ppsplus-bradh) wants to merge 4 commits into
langchain-ai:mainfrom
ppsplus-bradh:fix/connector-tool-resilience-and-operator-filter
Open

fix: connector tools fail gracefully + operator allow/deny tool filter#449
Brad Huffman (ppsplus-bradh) wants to merge 4 commits into
langchain-ai:mainfrom
ppsplus-bradh:fix/connector-tool-resilience-and-operator-filter

Conversation

@ppsplus-bradh

Copy link
Copy Markdown
Contributor

Closes #444.

What & why

A code --update --recursive documentation run could crash uncaught when a subagent invoked a connector-ingest tool without credentials configured — e.g. openwiki_ingest_connector({connectorId:"google"}) throwing Gmail refresh token is required for OAuth refresh. The throw escaped the tool call and propagated through the langgraph stream, terminating the whole run after a long, expensive execution that produced zero output.

Approach — a note on direction

The issue title suggested gating connector tools out of code mode. While implementing that, it became clear a blanket mode-gate over-removes git-repo, which this repo's own docs (openwiki/integrations/connectors.md) describe as legitimately relevant to code mode ("documenting a different target repo than the one being ingested from"). Deciding code mode should have no connectors would contradict documented behavior.

So this PR takes a more robust and less presumptuous route: make tool calling resilient, and let the operator decide which tools are exposed. This fixes the crash for all modes (not just code) and adds no behavior change by default.

Changes

  1. Resilience — connector tools fail gracefully. Every connector tool's func is wrapped so a thrown error becomes a structured, model-visible result (Tool error: … / {status:"error"}) instead of an uncaught crash. ingestConnector / ingestAllConnectors keep per-connector isolation (one failure doesn't abort the rest). Abort and LangGraph graph-interrupt signals are re-thrown, never swallowed, so cancellation and human-in-the-loop control flow keep working (isUncatchableError mirrors langgraph's is_bubble_up marker + GraphInterrupt/NodeInterrupt names + AbortError).

  2. Operator control — allow/deny tool filter. createOpenWikiConnectorTools(filter?) accepts {allow?, deny?} by tool name (deny wins; empty allow = all; default = all seven tools, no behavior change). Wired via --allow-tools / --deny-tools CLI flags (space and = forms) and OPENWIKI_ALLOW_TOOLS / OPENWIKI_DENY_TOOLS env vars (CLI wins over env) across both the run path and the scheduled ingest (cron) path — so operators can restrict tools in unattended CI runs, e.g. OPENWIKI_DENY_TOOLS=openwiki_ingest_connector,openwiki_ingest_all_connectors.

  3. Prompt gating by actual toolset. The "Connector ingestion discipline" block is only emitted when connector tools are actually present, so the agent is never instructed to call tools it doesn't have.

Relationship to #426 / #427

Complementary. #426 (unhandled-rejection crash) and #427 (connector tools should return errors, not throw) address the framework/tool-result layer generally; this PR adds the connector-specific graceful-failure wrapper (with abort/interrupt safety) plus operator-facing tool control and prompt consistency. The graceful-failure wrapper here overlaps #427's intent for connector tools specifically.

Testing

  • pnpm run typecheck, pnpm run lint:check, pnpm run format:check — all clean.
  • pnpm run test — 375 tests pass (34 files).
  • New/extended tests cover: allow/deny filtering (deny-wins, allow-whitelist, default-all), graceful failure returning structured errors, abort/interrupt correctly rejecting (not downgraded), ingestAllConnectors partial success, CLI/=-form flag parsing on both run and ingest, and prompt block presence/absence by toolset.

🤖 Generated with Claude Code

A `code --update --recursive` documentation run could crash uncaught when a
subagent invoked a connector-ingest tool without credentials configured (e.g.
`openwiki_ingest_connector({connectorId:"google"})` throwing "Gmail refresh
token is required for OAuth refresh"). The throw escaped the tool call and
propagated through the langgraph stream, terminating the whole run after it had
executed for a long time and produced no output.

Rather than hardcode-gating connector tools out of code mode (which would
over-remove `git-repo`, documented as relevant to code mode), this makes tool
calling resilient and gives operators explicit control:

- Resilience: every connector tool's func is wrapped so a thrown error becomes
  a structured, model-visible result instead of an uncaught crash. Abort and
  LangGraph graph-interrupt signals are re-thrown (never swallowed) so
  cancellation and human-in-the-loop control flow keep working. ingestConnector
  and ingestAllConnectors keep per-connector isolation with the same guard.
- Operator control: createOpenWikiConnectorTools accepts an allow/deny filter
  (deny wins; empty allow = all; default = all, no behavior change), wired via
  --allow-tools/--deny-tools CLI flags and OPENWIKI_ALLOW_TOOLS/
  OPENWIKI_DENY_TOOLS env vars across both the run and scheduled `ingest`
  (cron) paths.
- Prompt gating: the connector-ingestion discipline block is only emitted when
  connector tools are actually exposed, so the agent is never told to call
  tools it does not have.

Refs: langchain-ai#444 (also relates to langchain-ai#426, langchain-ai#427).

Co-Authored-By: Claude <noreply@anthropic.com>
@ppsplus-bradh
Brad Huffman (ppsplus-bradh) force-pushed the fix/connector-tool-resilience-and-operator-filter branch from e453fba to 578dcb8 Compare July 23, 2026 13:38
Comment thread src/connectors/tools.ts
}

// AbortController / cancellation.
if (error.name === "AbortError") {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice - great catch here.

Comment thread src/connectors/tools.ts

// LangGraph GraphBubbleUp marker (GraphInterrupt, NodeInterrupt, GraphDrained,
// ParentCommand all set this getter to true).
if (error.is_bubble_up === true) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch here as well. thanks for handling these

@colifran

Copy link
Copy Markdown
Collaborator

In general a big focus for me is trying to prevent unnecessary crashes and making things degrade gracefully so thank you for getting started on this.

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.

[Bug]: Connector ingest tools are exposed to code-mode agents (should be gated by run mode)

2 participants