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
- Do not remove it — express the change as a new invariant via addInvariant that supersedes the old rule
- Inspect governor.getInvariants() first and skip entries with immutable: true
- If you own the invariant definition and genuinely need removability, construct the MetaGovernor with that invariant registered as immutable: false from the start
- 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
- Model constitutional changes as new superseding invariants, not removals
- Register operational invariants with immutable: false if teardown must be possible
- Audit getInvariants() for immutable entries before writing cleanup code
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
- Cannot modify immutable invariant: ${change.target}
- Amendment rate limit exceeded: ${this.maxAmendmentsPerWindow
- Amendment not found: ${amendmentId}
- Cannot vote on amendment with status: ${amendment.status}
- Amendment already resolved: ${amendment.status}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/efb13e7527c153f3.
Report an issue: GitHub.