ruvnet/ruflo · error · AuthorizationPropagationError
scope-expired
scope-expired
Error message
scope expired at ${new Date(currentScope.expiresAt).toISOString()} What it means
wrapOutbound refuses to propagate a scope whose expiresAt (unix ms) is at or past the current time — AuthorizationPropagationError code 'scope-expired'. Expiry is copied unchanged from the holder and can never be extended (adding time requires a new grant from the original principal), so long-running delegation chains can simply run out of clock.
Source
Thrown at v3/@claude-flow/security/src/authorization/propagator.ts:159
* - newly granted servers MUST be a subset of `currentScope.grantedServers`
* - delegationDepth MUST decrement by ≥ 1 (must remain ≥ 0)
* - principalId is propagated unchanged
* - expiresAt cannot be extended; copied from the holder
*/
wrapOutbound<T>(
payload: T,
currentScope: AuthScope,
requested: { tools?: ReadonlyArray<string>; servers?: ReadonlyArray<string> } = {},
): SendMessageEnvelope<T> {
if (currentScope.delegationDepth <= 0) {
throw new AuthorizationPropagationError(
'depth-underflow',
`cannot delegate further — delegationDepth=${currentScope.delegationDepth}`,
);
}
const now = Date.now();
if (currentScope.expiresAt <= now) {
throw new AuthorizationPropagationError(
'scope-expired',
`scope expired at ${new Date(currentScope.expiresAt).toISOString()}`,
);
}
const reducedTools = subsetOrThrow(
currentScope.grantedTools,
requested.tools ?? currentScope.grantedTools,
'tools',
);
const reducedServers = subsetOrThrow(
currentScope.grantedServers,
requested.servers ?? currentScope.grantedServers,
'servers',
);
const reducedScope: AuthScope = {
principalId: currentScope.principalId,View on GitHub (pinned to fa13ee4ad6)
Solutions
- Obtain a fresh scope from the original principal — expiry cannot be extended in place
- Check Date.now() < scope.expiresAt (with margin) before sending, and refresh proactively at ~80% of TTL
- Sync clocks (NTP/chrony) across nodes if expiries fire suspiciously early
- Size the root scope's TTL to the whole workflow duration, not a single hop
Example fix
// before const envelope = propagator.wrapOutbound(msg, scope); // stale → scope-expired // after const usable = scope.expiresAt - Date.now() > 30_000 ? scope : await reissueScopeFromPrincipal(scope.principalId); // out-of-band re-grant const envelope = propagator.wrapOutbound(msg, usable);
Defensive patterns
Strategy: validation
Validate before calling
const TTL_MARGIN_MS = 30_000;
function scopeStillValid(scope: AuthScope, now = Date.now()): boolean {
return scope.expiresAt - now > TTL_MARGIN_MS;
}
if (!scopeStillValid(scope)) {
scope = await reissueScopeFromPrincipal(scope.principalId);
}
const envelope = propagator.wrapOutbound(msg, scope); Type guard
function isAuthPropagationError(e: unknown, code?: string): boolean {
return e instanceof Error && e.name === 'AuthorizationPropagationError'
&& (code === undefined || (e as { code?: string }).code === code);
} Try / catch
try {
return propagator.wrapOutbound(msg, scope);
} catch (e) {
if (isAuthPropagationError(e, 'scope-expired')) {
const fresh = await reissueScopeFromPrincipal(scope.principalId);
return propagator.wrapOutbound(msg, fresh);
}
throw e;
} Prevention
- Refresh scopes proactively at ~80% of TTL instead of waiting for the throw
- Size root-scope TTL to whole-workflow duration
- Keep nodes NTP-synced when scopes cross machine boundaries
When it happens
Trigger: A delegated task sits in a queue past the scope's TTL before the next hop; distributed deployments with clock skew, since expiresAt is compared against local Date.now(); resumed or retried workflows reusing a stale scope.
Common situations: Long swarm workflows outliving a short TTL; NTP drift between nodes making a valid scope look expired; checkpointed sessions restored hours later with their original scopes.
Related errors
- Cannot delegate expired capability ${capability.id}
- depth-underflow
- scope-cannot-grow
- delegation-depth-exhausted
- approval issuance requires an authenticated human identity a
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/0f5ad5007886ae65.
Report an issue: GitHub.