ruvnet/ruflo · error · Error
policy-${decision.enforcedOutcome}:${decision.reason}; recei
Error message
policy-${decision.enforcedOutcome}:${decision.reason}; receipt=${decision.receiptId} What it means
Thrown when ADR-324's authorizeMcpTool() returns a decision whose enforcedOutcome is not 'allowed' (e.g. denied, quarantined, require-approval). This is the single policy chokepoint on the local CLI/MCP dispatch path — every tool call passes through it in enforce mode. The message embeds the outcome, the human-readable reason, and a receiptId the operator can use to audit the decision in the policy log.
Source
Thrown at v3/@claude-flow/cli/src/mcp-client.ts:259
// Look up tool in registry
const tool = TOOL_REGISTRY.get(toolName);
if (!tool) {
throw new MCPClientError(
`MCP tool not found: ${toolName}`,
toolName
);
}
try {
// ADR-324: one policy chokepoint for every local CLI/MCP invocation.
// Policy administration is not exempt: authorization calls the engine
// directly, so there is no recursive MCP dispatch. In enforce mode an
// administrator must explicitly allow policy.* actions or use the local
// CLI bootstrap path.
const decision = await authorizeMcpTool(toolName, input, context, classifyMcpTool(toolName));
if (decision.enforcedOutcome !== 'allowed') {
throw new Error(`policy-${decision.enforcedOutcome}:${decision.reason}; receipt=${decision.receiptId}`);
}
// Call the tool handler
const result = await tool.handler(input, context);
// ADR-146 P2: scan every tool result for indirect-injection before it
// returns to the caller. The screen is opt-in via env (default off in
// 3.10.34 — flip to default in v4) so existing pipelines keep their
// exact behaviour while the call site is exercised by tests and
// adopters. Telemetry from the screen lands in the shared
// GuardrailEvent sink (P5).
return applyContentBoundaryGuardrail(toolName, result) as T;
} catch (error) {
// Wrap and re-throw with context
throw new MCPClientError(
`Failed to execute MCP tool '${toolName}': ${error instanceof Error ? error.message : String(error)}`,
toolName,
error instanceof Error ? error : undefined
);
}View on GitHub (pinned to 6b01dc5a68)
Solutions
- Look up the receiptId in the policy log / GuardrailEvent sink to see which rule fired and why.
- Run `npx ruflo policy status` and `npx ruflo policy verify` to see the active mode and the deny rules.
- Switch policy mode to 'observe' while building the allow-list: set the policy mode in your config (this logs without denying).
- Explicitly allow the action via the local CLI bootstrap path, or grant the policy.<action> claim for the calling principal.
- If the rule is wrong, edit the policy bundle and re-verify before switching back to 'enforce'.
Example fix
// before — call denied under enforce
await callMCPTool('file_write', { path: '/etc/x' });
// after — operator grants the claim, or you route via the bootstrap path
await runViaLocalCliBootstrap('file_write', { path: '/etc/x' }); Defensive patterns
Strategy: try-catch
Validate before calling
null
Type guard
null
Try / catch
try {
await callMCPTool(toolName, input, ctx);
} catch (e) {
const m = String(e?.message ?? '').match(/^policy-(?<outcome>[^:]+):(?<reason>[^;]*); receipt=(?<rid>[^\s]+)/);
if (m) {
// log the receipt for audit, decide on fallback vs surface
auditPolicyDenial(m.groups!.outcome, m.groups!.reason, m.groups!.rid);
}
throw e;
} Prevention
- Run policy in 'observe' mode first and review the deny log before switching to 'enforce'.
- Pre-allow the actions your pipeline uses (policy.<action> claims) in the bundle.
- Treat the receiptId as required context in your error-reporting pipeline.
When it happens
Trigger: Running with policy mode set to 'enforce' (not 'observe' or 'legacy') and invoking a tool whose action is not in the allow-list; calling a policy.* tool from the MCP path without the administrator bootstrap; a deny rule in the loaded policy bundle matching the tool name, input shape, or classification.
Common situations: A new policy bundle shipped with a stricter deny list; a tool was reclassified (e.g. from network-egress to filesystem-write) and now trips a different rule; an operator switched policy mode from legacy to enforce without pre-allowing the actions their pipeline uses; CI running under enforce with a stale allow-list.
Related errors
- approval issuance requires an authenticated human identity a
- browser/eval: script contains disallowed pattern: ${pattern.
- Issue ${input.issueId} is not claimed by ${input.claimantId}
- policy administration requires an interactive local terminal
- SSRF guard: invalid URL — ${rawUrl}
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/31bb7474ee38d234.
Report an issue: GitHub.