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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions