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

  1. Remove escalations from the worker envelope: network and destructive must remain false and delegationDepth 0
  2. Set maxConcurrency to 1 and expiresAt to a value at or before the orchestrator's timeout
  3. Keep actions/resources/tools at or below the defaults (wildcards or narrower explicit values)
  4. 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

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


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