ruvnet/ruflo · error
Cannot delegate expired capability ${capability.id}
Error message
Cannot delegate expired capability ${capability.id} What it means
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.
Source
Thrown at v3/@claude-flow/guidance/src/capabilities.ts:241
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,
};
// Apply optional further restrictions
if (restrictions?.actions) {
const originalSet = new Set(capability.actions);
delegated.actions = restrictions.actions.filter(a => originalSet.has(a));View on GitHub (pinned to fa13ee4ad6)
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
Example fix
// before
const child = authority.delegate(expiredCap, 'agent-b'); // throws
// after
function delegatableNow(cap: Capability) {
return cap.delegatable && !cap.revoked &&
(cap.expiresAt === null || cap.expiresAt > Date.now());
}
if (delegatableNow(expiredCap)) {
const child = authority.delegate(expiredCap, 'agent-b');
} else {
// re-grant, then delegate
} Defensive patterns
Strategy: validation
Validate before calling
if (capability.expiresAt !== null && capability.expiresAt <= Date.now()) {
// expired — re-grant before delegating
} Type guard
const isUnexpired = (c: Capability): boolean => c.expiresAt === null || c.expiresAt > Date.now();
Prevention
- 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
When it happens
Trigger: 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.
Common situations: Short-lived capability tokens for CI agents that expire mid-pipeline; retry queues replaying delegation after expiry; VM clock drift in distributed clusters.
Related errors
- Capability ${capability.id} is not delegatable
- Cannot delegate revoked capability ${capability.id}
- Cannot compose capabilities with different scopes: "${cap1.s
- Cannot compose capabilities with different resources: "${cap
- scope-expired
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/b0897c5cd6fc4bb5.
Report an issue: GitHub.