JuliusBrussee/caveman · error · Error

cave_harness_request_invalid

cave_harness_request_invalid

Error message

cave_harness_request_invalid

What it means

Inside `adapter.run`, after snapshotting the request and preparing locked execution, the frozen request must carry a `prompt` that is a string of length > 0 and a `runID` that is a string of length > 0; otherwise `cave_harness_request_invalid` throws. The same code is thrown by `snapshotRequest` when the request cannot be `structuredClone`d (unserializable fields) — only a real `AbortSignal` is exempted from cloning. It fires before any provider traffic, so nothing is billed.

Solutions

  1. Generate the run ID explicitly (e.g. `crypto.randomUUID()`) before calling `run`
  2. Validate `typeof prompt === 'string' && prompt.length > 0` at the call site
  3. Keep the request a plain serializable object; put only the AbortSignal outside cloneable data

Example fix

// before
await adapter.run({ ...base, prompt: userPrompt?.trim(), runID: session.lastRunID ?? '' })
// after
const runID = session.lastRunID ?? crypto.randomUUID();
if (!userPrompt?.trim()) throw new Error('empty prompt');
await adapter.run({ ...base, prompt: userPrompt, runID });
Defensive patterns

Strategy: validation

Validate before calling

function isRunnableRequest(r: Partial<HarnessRequest>): r is HarnessRequest {
  return typeof r.prompt === 'string' && r.prompt.length > 0 &&
    typeof r.runID === 'string' && r.runID.length > 0;
}
if (!isRunnableRequest(req)) throw new Error('harness request needs a non-empty prompt and runID');
// keep the request plain-cloneable: no class instances, functions, or DOM nodes
await adapter.run(req);

Type guard

function isRunnableRequest(r: Partial<HarnessRequest>): r is HarnessRequest {
  return typeof r.prompt === 'string' && r.prompt.length > 0 &&
    typeof r.runID === 'string' && r.runID.length > 0;
}

Try / catch

try {
  return await adapter.run(req);
} catch (e) {
  if (e instanceof Error && e.message === 'cave_harness_request_invalid') {
    // nothing was sent or billed — fix the caller's request shape (prompt/runID)
    // or remove non-cloneable fields, then rebuild the request
  }
  throw e;
}

Prevention

When it happens

Trigger: `adapter.run({ ...base, runID: "" })`; `prompt: undefined` from an optional chain that resolved empty; a request object containing class instances, functions, or DOM nodes that fail structuredClone.

Common situations: Forgetting to thread a generated run ID from the orchestration layer; building the prompt from an empty template or an empty user input; test harnesses reusing a mutable request object that got mutated between runs.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/02c6433bd3ec28ed. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/adapters.ts:127

  return Object.freeze({
    id,
    version: identity.adapterVersion,
    manifest,
    contractSHA256,
    async run(request: HarnessRequest): Promise<HarnessResult> {
      const frozenRequest = snapshotRequest(request);
      const prepared = prepareLockedHarnessExecution({
        build: frozenRequest.build,
        harness: id,
        adapterVersion: identity.adapterVersion,
        upstreamVersion: identity.upstreamVersion,
        contextIR: frozenRequest.contextIR,
        plan: frozenRequest.plan,
      });
      const { build, planSHA256, contextIRSHA256 } = prepared;
      if (typeof frozenRequest.prompt !== "string" || frozenRequest.prompt.length === 0 ||
          typeof frozenRequest.runID !== "string" || frozenRequest.runID.length === 0) {
        throw new Error("cave_harness_request_invalid");
      }
      if (isAborted(frozenRequest.signal)) throw new Error("cave_harness_aborted");
      validateTransformEvidence(frozenRequest, frozenRequest.plan);
      const execution = snapshotExecution(await invoke(frozenRequest));
      if (isAborted(frozenRequest.signal)) throw new Error("cave_harness_aborted");
      validateExecution(execution, frozenRequest.plan);
      return {
        terminal: execution.terminal,
        text: execution.text,
        provider: execution.provider,
        model: execution.model,
        inputTokens: execution.inputTokens,
        outputTokens: execution.outputTokens,
        cacheReadTokens: execution.cacheReadTokens,
        cacheWriteTokens: execution.cacheWriteTokens,
        reasoningTokens: execution.reasoningTokens,
        totalTokens: execution.totalTokens,
        costUsd: execution.costUsd,

View on GitHub (pinned to 766dce6b13)