{"record":{"id":"b0897c5cd6fc4bb5","repo":"ruvnet/ruflo","slug":"cannot-delegate-expired-capability-capability-id","errorCode":null,"errorMessage":"Cannot delegate expired capability ${capability.id}","messagePattern":"Cannot delegate expired capability (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/guidance/src/capabilities.ts","lineNumber":241,"sourceCode":"  delegate(\n    capability: Capability,\n    toAgentId: string,\n    restrictions?: Partial<Capability>,\n  ): Capability {\n    if (!capability.delegatable) {\n      throw new Error(\n        `Capability ${capability.id} is not delegatable`\n      );\n    }\n\n    if (capability.revoked) {\n      throw new Error(\n        `Cannot delegate revoked capability ${capability.id}`\n      );\n    }\n\n    if (capability.expiresAt !== null && capability.expiresAt <= Date.now()) {\n      throw new Error(\n        `Cannot delegate expired capability ${capability.id}`\n      );\n    }\n\n    const delegated: Capability = {\n      ...capability,\n      id: randomUUID(),\n      grantedBy: capability.grantedTo,\n      grantedTo: toAgentId,\n      grantedAt: Date.now(),\n      attestations: [],\n      parentCapabilityId: capability.id,\n    };\n\n    // Apply optional further restrictions\n    if (restrictions?.actions) {\n      const originalSet = new Set(capability.actions);\n      delegated.actions = restrictions.actions.filter(a => originalSet.has(a));","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/guidance/src/capabilities.ts#L223-L259","documentation":"delegate() compares the parent capability's `expiresAt` against Date.now() and refuses to delegate from an expired capability (expiresAt === null means no expiry). The child would inherit a tighter expiry anyway, so delegating from an already-expired parent is always invalid. This is a wall-clock check, so clock skew or a paused process can also trip it.","triggerScenarios":"Capability granted with a short `expiresAt` that elapsed before delegate() ran; long-lived process resumed (laptop sleep, container pause) past the expiry; system clock skewed forward on the delegating host.","commonSituations":"Short-lived capability tokens for CI agents that expire mid-pipeline; retry queues replaying delegation after expiry; VM clock drift in distributed clusters.","solutions":["Check `cap.expiresAt === null || cap.expiresAt > Date.now()` before calling delegate()","Re-grant the capability with a fresh expiresAt, then delegate","Extend grant lifetimes or implement renewal for long-running pipelines","Sync clocks (NTP) on hosts that mint and delegate capabilities"],"exampleFix":"// before\nconst child = authority.delegate(expiredCap, 'agent-b'); // throws\n\n// after\nfunction delegatableNow(cap: Capability) {\n  return cap.delegatable && !cap.revoked &&\n    (cap.expiresAt === null || cap.expiresAt > Date.now());\n}\nif (delegatableNow(expiredCap)) {\n  const child = authority.delegate(expiredCap, 'agent-b');\n} else {\n  // re-grant, then delegate\n}","handlingStrategy":"validation","validationCode":"if (capability.expiresAt !== null && capability.expiresAt <= Date.now()) {\n  // expired — re-grant before delegating\n}","typeGuard":"const isUnexpired = (c: Capability): boolean =>\n  c.expiresAt === null || c.expiresAt > Date.now();","tryCatchPattern":null,"preventionTips":["Check expiry immediately before every delegation call (clock moves)","Build renewal/re-grant into long-running pipelines","Keep host clocks NTP-synced where capabilities are minted and delegated"],"tags":["capabilities","authorization","expiry","guidance","clock-skew"],"backgroundTag":"capability-token-expired","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}