paperclipai/paperclip · error · SemanticDispatchFailure
operation_unavailable
operation_unavailable
Error message
No mock operation is bound to this descriptor.
What it means
SemanticDispatchFailure with code `operation_unavailable`, thrown from the semantic tools dispatcher when an input descriptor maps to a command kind that has no case in #applyMutation's switch. The dispatcher only binds a fixed set of mock operations; descriptors outside that set fall into the default branch and are rejected, meaning the mock control plane cannot execute the requested operation.
Solutions
- Use one of the dispatcher's supported operations (e.g. control_workspace_service, schedule_wake) instead of the unbound descriptor
- Add a case for the new operation kind in #applyMutation of packages/paperclip-runner/src/semantic-tools/dispatcher.ts
- Verify the operationId/descriptor spelling matches a registered mock operation
- Check whether the mock fixture version supports the operation; upgrade the fixture if newer operations are needed
Example fix
// before (dispatcher.ts, #applyMutation)
case "control_workspace_service": ... break;
// default: throw new SemanticDispatchFailure("operation_unavailable", ...)
// after — add the missing binding
case "schedule_wake":
command = { kind: "schedule_wake", taskId, reason: requiredString(input.reason), payload: optionalJson(input.payload), delayTicks: requiredNumber(input.delayTicks) };
break; Defensive patterns
Strategy: try-catch
Validate before calling
const SUPPORTED = new Set(["control_workspace_service", "schedule_wake" /* ...registered kinds */]);
if (!SUPPORTED.has(operation.kind)) {
throw new Error(`Operation '${operation.kind}' is not bound in the mock dispatcher`);
} Try / catch
try {
const outcome = await dispatcher.execute(input);
} catch (e) {
if (e instanceof SemanticDispatchFailure && e.code === "operation_unavailable") {
// fall back to a supported operation or skip with a clear test failure
} else throw e;
} Prevention
- Keep the dispatcher switch cases in sync with the semantic operation registry
- Add the new case to #applyMutation whenever a new CapabilitySemanticCommand kind is introduced
- Assert in tests that every registered descriptor has a bound mock operation
- Check the fixture's supported-operation list before invoking newer operations
When it happens
Trigger: #execute → #applyMutation with an operation whose normalized kind is not one of the handled cases (e.g. an unlisted or newly added semantic operation) so the switch hits `default`; calling a descriptor supported by the real control plane but not bound in the mock dispatcher.
Common situations: A new semantic operation was added upstream but the mock dispatcher switch was not extended; typo or version drift causing the descriptor to normalize to an unbound kind; tests exercising an operation the mock fixture does not implement.
Related errors
- ACPX runtime does not expose session goal controls
- ACPX session goal action
- Environment driver " " does not support duplex channels.
- Environment driver " " does not support orphan sandbox…
- fixture_state_invalid
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/adf169330b52f8a5.
Report an issue: GitHub.
Appendix: source
Thrown at packages/paperclip-runner/src/semantic-tools/dispatcher.ts:408
};
break;
case "request_approval":
command = { kind: "request_approval", taskId, approvalType: requiredString(input.approvalType), payload: optionalJson(input.payload) };
break;
case "decide_approval":
command = { kind: "decide_approval", taskId, approvalId: requiredString(input.approvalId), decision: requiredString(input.decision) as Extract<CapabilitySemanticCommand, { kind: "decide_approval" }>["decision"], note: requiredString(input.note) };
break;
case "comment_on_approval":
command = { kind: "comment_on_approval", taskId, approvalId: requiredString(input.approvalId), body: requiredString(input.body) };
break;
case "control_workspace_service":
command = { kind: "control_workspace_service", taskId, serviceId: requiredString(input.serviceId), action: requiredString(input.action) as Extract<CapabilitySemanticCommand, { kind: "control_workspace_service" }>["action"], url: nullableOptionalString(input.url) };
break;
case "schedule_wake":
command = { kind: "schedule_wake", taskId, reason: requiredString(input.reason) as Extract<CapabilitySemanticCommand, { kind: "schedule_wake" }>["reason"], payload: optionalJson(input.payload), delayTicks: requiredNumber(input.delayTicks) };
break;
default:
throw new SemanticDispatchFailure("operation_unavailable", "No mock operation is bound to this descriptor.");
}
const outcome = await this.port.tryApplyCommand({ runId, idempotencyKey, command });
if (operationId === "create_skill" && outcome.ok) {
const id = outcome.result.entityRefs.find(ref => ref.startsWith("skill:"))?.slice(6);
const skill = this.port.snapshot().skills?.find(candidate => candidate.id === id);
if (skill) return readSuccess(outcome.result.stateRevision, {
id: skill.id, name: skill.name, slug: skill.slug, description: skill.description,
versionId: skill.versionId, studioPath: `/skills/studio/${skill.id}`,
});
}
return commandOutcome(outcome);
}
#record(
context: CapabilitySemanticPolicyContext,
decision: CapabilitySemanticAuthorizationDecision,
callId: string | null,
input: unknown,View on GitHub (pinned to 3f1d897a7c)