{"record":{"id":"02c6433bd3ec28ed","repo":"JuliusBrussee/caveman","slug":"cave-harness-request-invalid","errorCode":"cave_harness_request_invalid","errorMessage":"cave_harness_request_invalid","messagePattern":"cave_harness_request_invalid","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/adapters.ts","lineNumber":127,"sourceCode":"  return Object.freeze({\n    id,\n    version: identity.adapterVersion,\n    manifest,\n    contractSHA256,\n    async run(request: HarnessRequest): Promise<HarnessResult> {\n      const frozenRequest = snapshotRequest(request);\n      const prepared = prepareLockedHarnessExecution({\n        build: frozenRequest.build,\n        harness: id,\n        adapterVersion: identity.adapterVersion,\n        upstreamVersion: identity.upstreamVersion,\n        contextIR: frozenRequest.contextIR,\n        plan: frozenRequest.plan,\n      });\n      const { build, planSHA256, contextIRSHA256 } = prepared;\n      if (typeof frozenRequest.prompt !== \"string\" || frozenRequest.prompt.length === 0 ||\n          typeof frozenRequest.runID !== \"string\" || frozenRequest.runID.length === 0) {\n        throw new Error(\"cave_harness_request_invalid\");\n      }\n      if (isAborted(frozenRequest.signal)) throw new Error(\"cave_harness_aborted\");\n      validateTransformEvidence(frozenRequest, frozenRequest.plan);\n      const execution = snapshotExecution(await invoke(frozenRequest));\n      if (isAborted(frozenRequest.signal)) throw new Error(\"cave_harness_aborted\");\n      validateExecution(execution, frozenRequest.plan);\n      return {\n        terminal: execution.terminal,\n        text: execution.text,\n        provider: execution.provider,\n        model: execution.model,\n        inputTokens: execution.inputTokens,\n        outputTokens: execution.outputTokens,\n        cacheReadTokens: execution.cacheReadTokens,\n        cacheWriteTokens: execution.cacheWriteTokens,\n        reasoningTokens: execution.reasoningTokens,\n        totalTokens: execution.totalTokens,\n        costUsd: execution.costUsd,","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/agent/src/adapters.ts#L109-L145","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","solutions":["Generate the run ID explicitly (e.g. `crypto.randomUUID()`) before calling `run`","Validate `typeof prompt === 'string' && prompt.length > 0` at the call site","Keep the request a plain serializable object; put only the AbortSignal outside cloneable data"],"exampleFix":"// before\nawait adapter.run({ ...base, prompt: userPrompt?.trim(), runID: session.lastRunID ?? '' })\n// after\nconst runID = session.lastRunID ?? crypto.randomUUID();\nif (!userPrompt?.trim()) throw new Error('empty prompt');\nawait adapter.run({ ...base, prompt: userPrompt, runID });","handlingStrategy":"validation","validationCode":"function isRunnableRequest(r: Partial<HarnessRequest>): r is HarnessRequest {\n  return typeof r.prompt === 'string' && r.prompt.length > 0 &&\n    typeof r.runID === 'string' && r.runID.length > 0;\n}\nif (!isRunnableRequest(req)) throw new Error('harness request needs a non-empty prompt and runID');\n// keep the request plain-cloneable: no class instances, functions, or DOM nodes\nawait adapter.run(req);","typeGuard":"function isRunnableRequest(r: Partial<HarnessRequest>): r is HarnessRequest {\n  return typeof r.prompt === 'string' && r.prompt.length > 0 &&\n    typeof r.runID === 'string' && r.runID.length > 0;\n}","tryCatchPattern":"try {\n  return await adapter.run(req);\n} catch (e) {\n  if (e instanceof Error && e.message === 'cave_harness_request_invalid') {\n    // nothing was sent or billed — fix the caller's request shape (prompt/runID)\n    // or remove non-cloneable fields, then rebuild the request\n  }\n  throw e;\n}","preventionTips":["Generate runID with crypto.randomUUID() in the layer that builds the request","Reject empty prompts at the API boundary before they reach the adapter","Keep request payloads structuredClone-safe; only the AbortSignal is exempt"],"tags":["typescript","validation","request-validation","harness-adapter"],"backgroundTag":"request-validation-failed","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}