JuliusBrussee/caveman · error · Error

cave_harness_signal_invalid

cave_harness_signal_invalid

Error message

cave_harness_signal_invalid

What it means

`snapshotRequest` validates an optional `AbortSignal`: if `request.signal` is defined, it must expose `aborted: boolean` and `addEventListener: function`. Anything else (a plain object masquerading as a signal, a stubbed signal) throws `cave_harness_signal_invalid`. Note: in the current source this throw happens inside the function's `try` block, so the enclosing `catch` rewrites it into `cave_harness_request_invalid` — callers will in practice observe the request_invalid code; the signal_invalid condition is still the underlying cause to fix.

Source

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

function deepFreeze<T>(value: T): T {
  if (value !== null && typeof value === "object") {
    Object.freeze(value);
    for (const child of Object.values(value)) deepFreeze(child);
  }
  return value;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return value !== null && typeof value === "object" && !Array.isArray(value);
}

function snapshotRequest(request: HarnessRequest): Readonly<HarnessRequest> {
  try {
    const { signal, ...serializable } = request;
    const snapshot = deepFreeze(structuredClone(serializable));
    if (signal === undefined) return snapshot;
    if (typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function") {
      throw new Error("cave_harness_signal_invalid");
    }
    return Object.freeze({ ...snapshot, signal });
  } catch {
    throw new Error("cave_harness_request_invalid");
  }
}

function snapshotExecution(value: HarnessExecution): Readonly<HarnessExecution> {
  if (!isRecord(value) ||
      !Array.isArray(value.evaluatedTransformIDs) ||
      !Array.isArray(value.appliedTransformIDs)) {
    throw new Error("cave_harness_incomplete_evidence");
  }
  return deepFreeze({
    terminal: value.terminal,
    text: value.text,
    provider: value.provider,
    model: value.model,

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Pass a real `AbortSignal` from `new AbortController().signal`, or omit `signal` entirely if cancellation is not needed.
  2. In tests, use Node's built-in `AbortController` rather than hand-rolled signal stubs.
  3. If you have a custom cancellation source, bridge it: create an `AbortController`, forward your events, and pass its signal.

Example fix

// before
await adapter.run({ ..., signal: { aborted: false } as AbortSignal });

// after
const controller = new AbortController();
await adapter.run({ ..., signal: controller.signal });
Defensive patterns

Strategy: type-guard

Validate before calling

function isAbortSignalLike(v: unknown): v is AbortSignal {
  return v === undefined || (
    typeof v === "object" && v !== null &&
    typeof (v as AbortSignal).aborted === "boolean" &&
    typeof (v as AbortSignal).addEventListener === "function"
  );
}
if (!isAbortSignalLike(request.signal)) throw new Error("pass a real AbortSignal or omit it");

Type guard

function isAbortSignalLike(v: unknown): v is AbortSignal {
  return typeof v === "object" && v !== null &&
    typeof (v as AbortSignal).aborted === "boolean" &&
    typeof (v as AbortSignal).addEventListener === "function";
}

Try / catch

try {
  await adapter.run(request);
} catch (err) {
  // note: current source rewrites this to cave_harness_request_invalid (error 29);
  // treat that code with a signal present as this condition
  if (err instanceof Error && err.message === "cave_harness_request_invalid" && request.signal) {
    // replace the fake signal with new AbortController().signal and retry
  }
}

Prevention

When it happens

Trigger: Setting `request.signal` to a mock object like `{ aborted: false }` without `addEventListener`; a custom abort implementation missing the DOM-standard methods; a signal polyfill that only partially implements the interface.

Common situations: Test doubles replacing `AbortController` with minimal fakes; older runtimes without full `AbortSignal`; passing a `EventTarget`-like cancellation token from a different library instead of a standard `AbortSignal`.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/0f3187157d91a122. Report an issue: GitHub.