mermaid-js/mermaid · error
State not found: ${trimmedId}
Error message
State not found: ${trimmedId} What it means
Defensive guard inside addState(): in the branch where states.has(trimmedId) was true, the immediate states.get(trimmedId) is re-checked and throws if it returns undefined. In single-threaded use this is effectively unreachable (the map cannot lose an entry between has() and get()), so seeing it signals concurrent mutation of the internal state map or a corrupted/deleted entry mid-parse.
Source
Thrown at packages/mermaid/src/diagrams/state/stateDb.ts:414
) {
const trimmedId = id?.trim();
if (!this.currentDocument.states.has(trimmedId)) {
log.info('Adding state ', trimmedId, descr);
this.currentDocument.states.set(trimmedId, {
stmt: STMT_STATE,
id: trimmedId,
descriptions: [],
type,
doc,
note,
classes: [],
styles: [],
textStyles: [],
});
} else {
const state = this.currentDocument.states.get(trimmedId);
if (!state) {
throw new Error(`State not found: ${trimmedId}`);
}
if (!state.doc) {
state.doc = doc;
}
if (!state.type) {
state.type = type;
}
}
if (descr) {
log.info('Setting state description', trimmedId, descr);
const descriptions = Array.isArray(descr) ? descr : [descr];
descriptions.forEach((des) => this.addDescription(trimmedId, des.trim()));
}
if (note) {
const doc2 = this.currentDocument.states.get(trimmedId);
if (!doc2) {View on GitHub (pinned to d93e9c88c0)
Solutions
- Give each render its own db instance (or call clear() before each parse) so the map is not shared.
- Avoid mutating the diagram db asynchronously while a parse is in flight.
- Audit custom parser hooks for any state deletion during addState.
- Reproduce in isolation; if it persists with a fresh db, report as a parser bug.
Example fix
// before — shared db reused concurrently mermaid.render(d1); mermaid.render(d2); // races on stateDb // after — isolate per render await mermaid.render(id1, d1); await mermaid.render(id2, d2);
Defensive patterns
Strategy: validation
Validate before calling
// Ensure the state exists before the second lookup style guards expect it
if (!stateDb.getDocument().states.has(trimmedId)) {
throw new Error(`State ${trimmedId} must be declared first`);
} Type guard
const isStateNotFound = (e): boolean => e instanceof Error && /^State not found:/.test(e.message);
Try / catch
try {
await mermaid.run({ nodes: [el] });
} catch (e) {
if (e instanceof Error && /^State not found:/.test(e.message)) {
// this is an internal invariant; isolate the db per render and retry
} else { throw e; }
} Prevention
- Use a fresh/isolated db per render; call clear() before each parse.
- Never mutate the db concurrently across diagrams.
- Treat this error as a bug indicator and report if reproducible.
When it happens
Trigger: The states Map is mutated from another context (shared db reused across diagrams without clear(), or async/worker access) so the entry present at has() is gone at get(); a custom parser extension that deletes states during traversal.
Common situations: Reusing a singleton stateDb across concurrently-rendered diagrams; clearing the db on one thread while another parses; forked/patched parser code that removes states; very rare race in an embedding host.
Related errors
- Line definition not found
- Position not found for branch ${branch.name}
- Groups within groups are not allowed in Kanban diagrams
- start should have been set during first phase
- end should have been set during first phase
AI-assisted analysis of mermaid-js/mermaid@d93e9c88c0 (2026-08-12).
Data as JSON: /api/errors/39d0c0122a985f59.
Report an issue: GitHub.