nexu-io/open-design · warning · Error

The Open Design brief nonce is invalid.

Error message

The Open Design brief nonce is invalid.

What it means

Thrown when the nonce supplied to confirm_brief does not match the nonce stored on the draft for that briefDraftId. The nonce is a 24-byte random hex generated per draft by collect_brief and acts as a possession token binding the confirm to the exact draft that created it. A mismatch means the (briefDraftId, nonce) pair did not come from the same collect_brief call.

Source

Thrown at apps/daemon/src/mcp-brief.ts:443

      };
    },

    confirm(input) {
      const at = now();
      pruneExpired(at);
      const briefDraftId = readRequiredString(
        input.briefDraftId,
        'briefDraftId',
      );
      const nonce = readRequiredString(input.nonce, 'nonce');
      const draft = drafts.get(briefDraftId);
      if (!draft) {
        throw new Error(
          'The Open Design brief has expired or is unknown. Call collect_brief again.',
        );
      }
      if (draft.nonce !== nonce) {
        throw new Error('The Open Design brief nonce is invalid.');
      }
      const submittedAnswers = readAnswerRecord(input.answers, 'answers');
      const mergedAnswers: UnknownRecord = {
        ...draft.knownAnswers,
        ...submittedAnswers,
      };
      const decision = collectOpenDesignBrief({
        artifactType: draft.artifactType,
        knownAnswers: mergedAnswers,
      });
      if (!decision.complete) {
        const missing = decision.questions.map((question) => question.id);
        throw new Error(
          `The Open Design brief is incomplete. Missing: ${missing.join(', ')}.`,
        );
      }
      const confirmationAnswersDigest = stableAnswerDigest(decision.answers);
      if (draft.confirmation) {

View on GitHub (pinned to 5be4028344)

Solutions

  1. Use the nonce returned by the exact collect_brief response that produced briefDraftId.
  2. Pass briefDraftId and nonce together from the collect_brief result without transforming them.

Example fix

// before
confirm_brief({ briefDraftId, nonce: oldNonce, answers }) // throws [361]

// after
const { briefDraftId, nonce } = await collect_brief({ artifactType: 'website' })
await confirm_brief({ briefDraftId, nonce, answers })
Defensive patterns

Strategy: retry

Type guard

function isBriefDraftPair(x: unknown): x is { briefDraftId: string; nonce: string } {
  return !!x
    && typeof (x as any)?.briefDraftId === 'string'
    && typeof (x as any)?.nonce === 'string'
    && /^[0-9a-f]{48}$/i.test((x as any).nonce);
}

Try / catch

try {
  await confirmBrief(input);
} catch (e) {
  if (/nonce is invalid/i.test(e.message)) {
    const fresh = await collectBrief({ artifactType });
    return confirmBrief({ ...input, briefDraftId: fresh.briefDraftId, nonce: fresh.nonce });
  }
  throw e;
}

Prevention

When it happens

Trigger: Pairing a briefDraftId from one collect_brief call with a nonce from another; replaying an old nonce after re-collecting; truncating or mutating the nonce string.

Common situations: An agent cached the nonce separately from briefDraftId and they desynced; copy-pasting only one of the two fields.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/59bcb12dc5d99205. Report an issue: GitHub.