ruvnet/ruflo · error
worker ${worker.id} capability envelope cannot expand
Error message
worker ${worker.id} capability envelope cannot expand What it means
resolveWorkerEnvelope merges a worker's requested capabilityEnvelope over a fixed synthetic parent envelope (actions/resources/tools '*', maxConcurrency 1, network false, destructive false, delegationDepth 0, expiresAt now + config.timeout). Delegation may only reduce scope: every child axis must be a subset of the parent's, and any requested value that expands it throws for that worker id. Because the parent pins delegationDepth to 0 and network/destructive to false, requesting delegationDepth > 0, network: true, or destructive: true always fails; maxConcurrency > 1 and expiresAt beyond the orchestrator timeout also fail.
Source
Thrown at v3/@claude-flow/codex/src/dual-mode/orchestrator.ts:585
!patterns?.length
|| patterns.some((pattern) => pattern === '*' || pattern === value
|| (pattern.endsWith('*') && value.startsWith(pattern.slice(0, -1))))
);
const subset = (values: string[] | undefined, patterns: string[] | undefined): boolean => (
!patterns?.length || (!!values?.length && values.every((value) => matches(patterns, value)))
);
const valid = subset(child.actions, parent.actions)
&& subset(child.resources, parent.resources)
&& subset(child.tools, parent.tools)
&& (parent.maxConcurrency === undefined
|| (child.maxConcurrency !== undefined && child.maxConcurrency <= parent.maxConcurrency))
&& (parent.expiresAt === undefined
|| (child.expiresAt !== undefined && child.expiresAt <= parent.expiresAt))
&& (parent.delegationDepth === undefined
|| (child.delegationDepth !== undefined && child.delegationDepth <= parent.delegationDepth))
&& !(child.network === true && parent.network !== true)
&& !(child.destructive === true && parent.destructive !== true);
if (!valid) throw new Error(`worker ${worker.id} capability envelope cannot expand`);
return child;
}
private workerEnvironment(worker: WorkerConfig): NodeJS.ProcessEnv {
const env: NodeJS.ProcessEnv = {};
const sensitive = /(?:^|_)(?:API_?KEY|KEY|SECRET|TOKEN|PASSWORD|CREDENTIALS?)$/i;
for (const [name, value] of Object.entries(process.env)) {
if (sensitive.test(name)
|| name.startsWith('CLAUDE_FLOW_POLICY_')
|| name === 'CLAUDE_FLOW_PRINCIPAL_ID') continue;
env[name] = value;
}
env.FORCE_COLOR = '0';
env.CLAUDE_FLOW_DB_PATH = this.config.memoryDbPath;
env.CLAUDE_FLOW_PRINCIPAL_ID = `agent:${worker.id}`;
env.CLAUDE_FLOW_CAPABILITY_ENVELOPE = JSON.stringify(
this.resolveWorkerEnvelope(worker),
);View on GitHub (pinned to fa13ee4ad6)
Solutions
- Remove escalations from the worker envelope: network and destructive must remain false and delegationDepth 0
- Set maxConcurrency to 1 and expiresAt to a value at or before the orchestrator's timeout
- Keep actions/resources/tools at or below the defaults (wildcards or narrower explicit values)
- If a worker genuinely needs more capability, raise it at the orchestrator level (e.g. a longer timeout) — never by widening the child envelope
Example fix
// before
worker.capabilityEnvelope = {
network: true, // parent has network: false
destructive: true, // parent has destructive: false
maxConcurrency: 4, // parent pins 1
delegationDepth: 1, // parent pins 0
};
// after
worker.capabilityEnvelope = {
network: false,
destructive: false,
maxConcurrency: 1,
delegationDepth: 0,
expiresAt: Date.now() + 60_000, // at or before orchestrator timeout
}; Defensive patterns
Strategy: validation
Validate before calling
function isSubsetEnvelope(child: WorkerCapabilityEnvelope, parent: WorkerCapabilityEnvelope): boolean {
const covered = (patterns: string[], value: string) =>
patterns.some((p) => p === '*' || p === value || (p.endsWith('*') && value.startsWith(p.slice(0, -1))));
return (
child.actions.every((a) => covered(parent.actions, a))
&& child.resources.every((r) => covered(parent.resources, r))
&& child.tools.every((t) => covered(parent.tools, t))
&& child.maxConcurrency <= parent.maxConcurrency
&& child.expiresAt <= parent.expiresAt
&& child.delegationDepth <= parent.delegationDepth
&& !(child.network && !parent.network)
&& !(child.destructive && !parent.destructive)
);
}
// parent defaults: maxConcurrency 1, delegationDepth 0, network/destructive false, expiresAt = now + timeout Try / catch
Catch around worker registration; on 'capability envelope cannot expand', log the worker id and prune the offending fields (network/destructive/delegationDepth/maxConcurrency/expiresAt) rather than retrying — the check is deterministic.
Prevention
- Never hand-write worker envelopes wider than defaults; start empty and add only reductions
- Compute expiresAt relative to the orchestrator timeout, never a fixed wall-clock constant
- Encode the subset rule in a shared validator used by every component that creates worker envelopes
When it happens
Trigger: worker.capabilityEnvelope set with network: true or destructive: true; delegationDepth greater than 0; maxConcurrency greater than 1; expiresAt later than Date.now() + config.timeout; or actions/resources/tools patterns not covered by the parent's wildcard patterns.
Common situations: Porting worker envelopes from a system where the parent process held broad grants (here the parent is a synthetic default, not your process); trying to give spawned workers network access by default; setting a long expiresAt that overruns the orchestrator timeout.
Related errors
- capability-envelope-cannot-grow
- delegation-depth-exhausted
- unknown game "${key}". Known: ${Object.keys(GAMES).join(', '
- unknown strategy "${name}". Available: ${roster.map((r) => r
- signBacktestArtifact: privateKey must be 32 bytes (got ${pri
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/a10897094c80cb6f.
Report an issue: GitHub.