Skip to content

"No response received from Claude" — root cause & proposed fix #1

Description

@slacki-ai

Root Cause

The error "No response received from Claude." is returned by consumeResponse() in src/claude/response.ts whenever the SDK generator finishes without ever yielding a type === "result" message.

This is almost always triggered by the 3-minute inactivity timeout in withInactivityTimeout():

const INACTIVITY_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes

This function races every iterator.next() call against a setTimeout. If the Claude SDK goes silent for 3 minutes — no messages of any kind — it stops iteration early, result stays undefined, and the error is returned.

Why does the SDK go silent? The SDK only emits messages between tool calls. A single long-running tool call (slow shell command, large file operation, network request, npm install, model training run, etc.) produces complete silence for its entire duration. If that exceeds 3 minutes, the timeout fires mid-tool-call and the response is lost.

Secondary failure mode (silent)

In src/slack/events.ts, consumeResponse is wrapped in .catch(() => null). If it throws (rather than returning the error object), the failure is swallowed silently — the thinking indicator disappears but nothing is posted to the user.


Recommended Fix

The core tension: we want a timeout to prevent genuine doom loops (stuck processes, hung sessions), but we don't want to interrupt legitimate long-running work.

Proposed approach: distinguish total timeout from inactivity timeout

Keep two separate timeout values:

Timeout Purpose Suggested default
Inactivity timeout No SDK messages at all — likely hung 10–15 min
Hard cap (optional) Absolute ceiling regardless of activity e.g. 30–60 min, or disabled

The inactivity timeout already exists — just increase INACTIVITY_TIMEOUT_MS from 3 min to something more generous (10–15 min covers nearly all realistic tool calls without allowing true doom loops to run forever).

Allow per-channel/per-message override via env var or config

Add an env var that operators can set when deploying:

const INACTIVITY_TIMEOUT_MS =
  parseInt(process.env.CLAUDE_INACTIVITY_TIMEOUT_MS ?? "") ||
  10 * 60 * 1000; // default: 10 minutes

Power users doing long jobs (training runs, big refactors) can bump it; cautious deployments can lower it.

Fix the silent failure path

The .catch(() => null) in events.ts should post an error message rather than silently swallowing the failure:

const consumePromise = consumeResponse(gen).catch((err) => {
  console.warn(`[${threadKey}] consumeResponse error: ${err}`);
  return { sessionId: "", text: `Error: ${err instanceof Error ? err.message : String(err)}`, isError: true, costUsd: 0 };
});

Summary of recommended changes

  1. Increase default INACTIVITY_TIMEOUT_MS from 3 min → 10–15 min
  2. Make it configurable via CLAUDE_INACTIVITY_TIMEOUT_MS env var
  3. Fix the silent .catch(() => null) to surface errors to the user

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions