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

  1. Look up the receiptId in the policy log / GuardrailEvent sink to see which rule fired and why.
  2. Run `npx ruflo policy status` and `npx ruflo policy verify` to see the active mode and the deny rules.
  3. Switch policy mode to 'observe' while building the allow-list: set the policy mode in your config (this logs without denying).
  4. Explicitly allow the action via the local CLI bootstrap path, or grant the policy.<action> claim for the calling principal.
  5. 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

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


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/31bb7474ee38d234. Report an issue: GitHub.