ruvnet/ruflo · error

Cannot delegate revoked capability ${capability.id}

Error message

Cannot delegate revoked capability ${capability.id}

What it means

delegate() refuses to derive children from a capability whose `revoked` flag is set, preventing revocation from being laundered by re-delegation. The check runs after the delegatable check, so this error confirms the parent was delegatable at grant time but has since been revoked. Note the error reflects the capability object's current state — a stale copy captured before revocation will not show it.

Source

Thrown at v3/@claude-flow/guidance/src/capabilities.ts:235

   * Creates a child capability with the new grantedTo agent. The parent
   * capability must have delegatable=true. Optional further restrictions
   * can be applied during delegation.
   *
   * @throws Error if the capability is not delegatable
   */
  delegate(
    capability: Capability,
    toAgentId: string,
    restrictions?: Partial<Capability>,
  ): Capability {
    if (!capability.delegatable) {
      throw new Error(
        `Capability ${capability.id} is not delegatable`
      );
    }

    if (capability.revoked) {
      throw new Error(
        `Cannot delegate revoked capability ${capability.id}`
      );
    }

    if (capability.expiresAt !== null && capability.expiresAt <= Date.now()) {
      throw new Error(
        `Cannot delegate expired capability ${capability.id}`
      );
    }

    const delegated: Capability = {
      ...capability,
      id: randomUUID(),
      grantedBy: capability.grantedTo,
      grantedTo: toAgentId,
      grantedAt: Date.now(),
      attestations: [],
      parentCapabilityId: capability.id,

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Re-fetch the capability's current state from the authority before delegating and abort if revoked
  2. If revocation was accidental, re-grant a fresh capability and delegate from that
  3. Sequence revocation and delegation through one coordinator so they cannot interleave

Example fix

// before
const child = authority.delegate(cachedCap, 'agent-b'); // cachedCap was revoked meanwhile → throws

// after
const fresh = authority.get(cachedCap.id);
if (!fresh || fresh.revoked) throw new Error('capability no longer valid');
const child = authority.delegate(fresh, 'agent-b');
Defensive patterns

Strategy: validation

Validate before calling

const fresh = authority.get(capability.id);
if (!fresh || fresh.revoked) {
  // capability is gone — abort delegation
}

Type guard

const isActive = (c: Capability): boolean =>
  !c.revoked && (c.expiresAt === null || c.expiresAt > Date.now());

Prevention

When it happens

Trigger: Some component calls revoke() on the capability, then another calls delegate() with the same (or a stale copy of the) capability; revocation and delegation racing in concurrent workflows.

Common situations: Offboarding/revocation workflows racing against in-flight delegation jobs; cached capability objects captured before a revocation event; audit tooling re-running delegation scripts against revoked creds.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/7290a2df7eb8e7b1. Report an issue: GitHub.