{"record":{"id":"59bcb12dc5d99205","repo":"nexu-io/open-design","slug":"the-open-design-brief-nonce-is-invalid","errorCode":null,"errorMessage":"The Open Design brief nonce is invalid.","messagePattern":"The Open Design brief nonce is invalid\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/daemon/src/mcp-brief.ts","lineNumber":443,"sourceCode":"      };\n    },\n\n    confirm(input) {\n      const at = now();\n      pruneExpired(at);\n      const briefDraftId = readRequiredString(\n        input.briefDraftId,\n        'briefDraftId',\n      );\n      const nonce = readRequiredString(input.nonce, 'nonce');\n      const draft = drafts.get(briefDraftId);\n      if (!draft) {\n        throw new Error(\n          'The Open Design brief has expired or is unknown. Call collect_brief again.',\n        );\n      }\n      if (draft.nonce !== nonce) {\n        throw new Error('The Open Design brief nonce is invalid.');\n      }\n      const submittedAnswers = readAnswerRecord(input.answers, 'answers');\n      const mergedAnswers: UnknownRecord = {\n        ...draft.knownAnswers,\n        ...submittedAnswers,\n      };\n      const decision = collectOpenDesignBrief({\n        artifactType: draft.artifactType,\n        knownAnswers: mergedAnswers,\n      });\n      if (!decision.complete) {\n        const missing = decision.questions.map((question) => question.id);\n        throw new Error(\n          `The Open Design brief is incomplete. Missing: ${missing.join(', ')}.`,\n        );\n      }\n      const confirmationAnswersDigest = stableAnswerDigest(decision.answers);\n      if (draft.confirmation) {","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/mcp-brief.ts#L425-L461","documentation":"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.","triggerScenarios":"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.","commonSituations":"An agent cached the nonce separately from briefDraftId and they desynced; copy-pasting only one of the two fields.","solutions":["Use the nonce returned by the exact collect_brief response that produced briefDraftId.","Pass briefDraftId and nonce together from the collect_brief result without transforming them."],"exampleFix":"// before\nconfirm_brief({ briefDraftId, nonce: oldNonce, answers }) // throws [361]\n\n// after\nconst { briefDraftId, nonce } = await collect_brief({ artifactType: 'website' })\nawait confirm_brief({ briefDraftId, nonce, answers })","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isBriefDraftPair(x: unknown): x is { briefDraftId: string; nonce: string } {\n  return !!x\n    && typeof (x as any)?.briefDraftId === 'string'\n    && typeof (x as any)?.nonce === 'string'\n    && /^[0-9a-f]{48}$/i.test((x as any).nonce);\n}","tryCatchPattern":"try {\n  await confirmBrief(input);\n} catch (e) {\n  if (/nonce is invalid/i.test(e.message)) {\n    const fresh = await collectBrief({ artifactType });\n    return confirmBrief({ ...input, briefDraftId: fresh.briefDraftId, nonce: fresh.nonce });\n  }\n  throw e;\n}","preventionTips":["Treat briefDraftId and nonce as one atomic token returned by collect_brief.","Never split or cache them independently.","Pass both straight through without transformation."],"tags":["mcp","brief","nonce","auth"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}