ruvnet/ruflo · error

Amendment already resolved: ${amendment.status}

Error message

Amendment already resolved: ${amendment.status}

What it means

resolveAmendment() only operates on amendments whose status is still 'proposed'. Resolution mutates status in place to 'approved' or 'rejected' (based on supermajority threshold and requiredApprovals) without removing the entry, so a second resolve call finds a non-proposed status and throws 'Amendment already resolved' with the terminal status in the message.

Source

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

      throw new Error(`Amendment not found: ${amendmentId}`);
    }
    if (amendment.status !== 'proposed') {
      throw new Error(`Cannot vote on amendment with status: ${amendment.status}`);
    }

    amendment.votes.set(voterId, approve);
  }

  /**
   * Resolve an amendment (check if supermajority reached)
   */
  resolveAmendment(amendmentId: string): Amendment {
    const amendment = this.amendments.get(amendmentId);
    if (!amendment) {
      throw new Error(`Amendment not found: ${amendmentId}`);
    }
    if (amendment.status !== 'proposed') {
      throw new Error(`Amendment already resolved: ${amendment.status}`);
    }

    const totalVotes = amendment.votes.size;
    const approvals = Array.from(amendment.votes.values()).filter((v) => v).length;
    const approvalRate = totalVotes > 0 ? approvals / totalVotes : 0;

    if (approvalRate >= this.supermajorityThreshold && approvals >= amendment.requiredApprovals) {
      amendment.status = 'approved';
    } else {
      amendment.status = 'rejected';
    }

    return amendment;
  }

  /**
   * Enact an approved amendment
   * Returns true if enacted successfully

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Treat the Amendment return value of the first resolveAmendment() as the verdict and persist it — do not re-resolve
  2. Make retries idempotent: check getPendingAmendments() (which only lists status 'proposed') before resolving
  3. Catch this specific message and return the previously recorded outcome instead of failing the retry
  4. In multi-worker setups, route resolution through a single owner

Example fix

// before
function settle(id: string) {
  return governor.resolveAmendment(id); // throws on retry
}
// after
const verdicts = new Map<string, Amendment>();
function settle(id: string): Amendment {
  if (verdicts.has(id)) return verdicts.get(id)!;
  const pending = governor.getPendingAmendments().some(a => a.id === id);
  const verdict = pending ? governor.resolveAmendment(id) : null;
  if (verdict) verdicts.set(id, verdict);
  return verdict!;
}
Defensive patterns

Strategy: validation

Validate before calling

const stillProposed = governor.getPendingAmendments().some(a => a.id === amendmentId);
if (!stillProposed) {
  // already approved/rejected (still in map but not pending), or terminal
  const hist = governor.getAmendmentHistory().find(a => a.id === amendmentId);
  return hist ?? null;
}
return governor.resolveAmendment(amendmentId);

Type guard

function needsResolution(governor: MetaGovernor, id: string): boolean {
  return governor.getPendingAmendments().some(a => a.id === id); // only 'proposed' need resolving
}

Try / catch

try {
  governor.resolveAmendment(amendmentId);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Amendment already resolved')) {
    return; // verdict already recorded; do not fail the retry job
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling resolveAmendment() twice on the same ID; retry logic that re-runs resolve after a timeout even though the first call succeeded; a coordinator and a cleanup job both trying to resolve.

Common situations: At-least-once task redelivery without idempotency keys; polling loops that resolve on a timer; test helpers that resolve then the test body resolves again.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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