nexu-io/open-design · warning · Error

The Open Design brief has expired or is unknown. Call collec

Error message

The Open Design brief has expired or is unknown. Call collect_brief again.

What it means

Thrown by the local MCP brief store's confirm step when no in-memory draft exists for the supplied briefDraftId. Drafts live in a process-local Map pruned by expiresAt (TTL) on every confirm call, so any draft that aged out, was never created, or belonged to a previous daemon process is gone. The message directs the caller to start over with collect_brief, which mints a fresh briefDraftId and nonce.

Source

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

        expiresAt,
        ...(pluginWorkflowId ? { pluginWorkflowId } : {}),
        ...(externalPluginContext ? { externalPluginContext } : {}),
        questionForm: questionFormsByLocale[locale],
        questionFormsByLocale,
      };
    },

    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(

View on GitHub (pinned to 5be4028344)

Solutions

  1. Call collect_brief again to obtain a new briefDraftId and nonce, then confirm immediately within the TTL.
  2. Confirm in the same daemon process and same MCP session that created the draft.
  3. When automating, keep collect then confirm in a tight sequence and never persist briefDraftId across restarts.

Example fix

// before
confirm_brief({ briefDraftId: staleId, nonce: staleNonce, answers }) // throws [360]

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

Strategy: retry

Validate before calling

const draft = store.peek(briefDraftId);
if (!draft || Date.now() > draft.expiresAt) {
  // draft gone or expired -> must re-collect
  const fresh = await collectBrief({ artifactType });
  return confirmBrief({ ...input, briefDraftId: fresh.briefDraftId, nonce: fresh.nonce });
}

Try / catch

try {
  await confirmBrief(input);
} catch (e) {
  if (/expired or is unknown/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: Calling confirm_brief after the draft TTL elapsed; calling it after a daemon restart (drafts are in-memory only and not persisted); passing a briefDraftId from a different Open Design MCP session; passing a typo or stale copied id.

Common situations: Long pause between collect_brief and confirm_brief in an agent loop exceeding the TTL; daemon auto-restarted mid-flow; two MCP clients each running their own brief and mixing ids.

Related errors


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