ruvnet/ruflo · error

Cannot remove immutable invariant: ${id}

Error message

Cannot remove immutable invariant: ${id}

What it means

MetaGovernor.removeInvariant(id) throws when the targeted constitutional invariant was registered with immutable: true. This is deliberate tamper protection: immutable invariants are the constitution's floor and the normal API offers no flag, override, or force option to remove them. Unknown IDs return false instead of throwing.

Source

Thrown at v3/@claude-flow/guidance/src/meta-governance.ts:328

  }

  /**
   * Add a constitutional invariant
   */
  addInvariant(invariant: ConstitutionalInvariant): void {
    this.invariants.set(invariant.id, invariant);
  }

  /**
   * Remove an invariant (only if not immutable)
   */
  removeInvariant(id: string): boolean {
    const invariant = this.invariants.get(id);
    if (!invariant) {
      return false;
    }
    if (invariant.immutable) {
      throw new Error(`Cannot remove immutable invariant: ${id}`);
    }
    return this.invariants.delete(id);
  }

  /**
   * Check all constitutional invariants against current state
   */
  checkAllInvariants(state: GovernanceState): InvariantReport {
    const results: Array<{ invariant: ConstitutionalInvariant; result: InvariantCheckResult }> = [];
    let allHold = true;

    for (const invariant of this.invariants.values()) {
      const result = invariant.check(state);
      results.push({ invariant, result });
      if (!result.holds) {
        allHold = false;
      }
    }

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Do not remove it — express the change as a new invariant via addInvariant that supersedes the old rule
  2. Inspect governor.getInvariants() first and skip entries with immutable: true
  3. If you own the invariant definition and genuinely need removability, construct the MetaGovernor with that invariant registered as immutable: false from the start
  4. Reserve removeInvariant for non-immutable, operational invariants only

Example fix

// before
governor.removeInvariant('no-unverified-write'); // immutable -> throws
// after
const inv = governor.getInvariants().find(i => i.id === 'no-unverified-write');
if (inv?.immutable) {
  governor.addInvariant({ ...inv, id: 'no-unverified-write-v2', description: 'Relaxed rule' });
} else {
  governor.removeInvariant('no-unverified-write');
}
Defensive patterns

Strategy: type-guard

Validate before calling

const target = governor.getInvariants().find(i => i.id === id);
if (!target) return false; // removeInvariant returns false for unknown ids
if (target.immutable) {
  throw new Error(`Invariant ${id} is immutable; supersede it with addInvariant instead`);
}
return governor.removeInvariant(id);

Type guard

function isRemovableInvariant(governor: MetaGovernor, id: string): boolean {
  const inv = governor.getInvariants().find(i => i.id === id);
  return inv !== undefined && inv.immutable === false;
}

Prevention

When it happens

Trigger: Calling removeInvariant() on a core/default invariant flagged immutable; calling it on an invariant you yourself added with immutable: true; cleanup code that tries to sweep all invariants via getInvariants() without checking the flag.

Common situations: Governance tests that add invariants then try to tear them down; runtime code attempting to loosen constitutional constraints after deployment; refactors that assume all invariants are removable.

Related errors


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