GraphiteEditor/Graphite · error
Active document is missing from document ids
Error message
Active document is missing from document ids
What it means
document_index does self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids"). PortfolioMessageHandler keeps documents in a HashMap plus an order Vec (document_ids); this expect asserts the two stay in sync. It panics when a caller asks for the index of a document (usually the active one) whose id is absent from the ordering Vec - a state-desync bug where a document was removed from one structure but not the other.
Source
Thrown at editor/src/messages/portfolio/portfolio_message_handler.rs:1929
compare_storage_against_runtime(&gdd, &legacy_network, byte_store.as_ref(), document_id).await;
}
Message::Portfolio(PortfolioMessage::DocumentStorageMounted {
document_id,
reopened,
gdd: Some(gdd),
})
};
future.into()
}
/// Returns an iterator over the open documents in order.
pub fn ordered_document_iterator(&self) -> impl Iterator<Item = &DocumentMessageHandler> {
self.document_ids.iter().filter_map(|id| self.document(*id))
}
fn document_index(&self, document_id: DocumentId) -> usize {
self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids")
}
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
let Some(document_id) = self.active_document_id else {
return Err("No active document".to_string());
};
let Some(active_document) = self.documents.get_mut(&document_id) else {
return Err("No active document".to_string());
};
let result = self.executor.poll_node_graph_evaluation(active_document, document_id, responses);
if result.is_err() {
let error = r#"
<rect x="50%" y="50%" width="460" height="100" transform="translate(-230 -50)" rx="4" fill="var(--color-warning-yellow)" />
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="18" fill="var(--color-2-mildblack)">
<tspan x="50%" dy="-24" font-weight="bold">The document cannot render in its current state.</tspan>
<tspan x="50%" dy="24">Undo to go back, if available, or check for error details</tspan>
<tspan x="50%" dy="24">by clicking the <tspan font-style="italic">Node Graph</tspan> button up at the top right.</tspan>View on GitHub (pinned to c507b35645)
Solutions
- Change document_index to return Option<usize> (or use position().unwrap_or default with a logged error) so desync degrades instead of panicking
- Audit every mutation of self.documents to also mutate self.document_ids in the same message handler
- Add a debug assertion after close/open messages that the map keys and the Vec contents match exactly
Example fix
// before
fn document_index(&self, document_id: DocumentId) -> usize {
self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids")
}
// after
fn document_index(&self, document_id: DocumentId) -> Option<usize> {
self.document_ids.iter().position(|id| id == &document_id)
} Defensive patterns
Strategy: validation
Validate before calling
if let Some(index) = self.document_ids.iter().position(|id| id == &document_id) {
// use index
} else {
log::error!("document {document_id:?} missing from document_ids; desync");
} Prevention
- Update self.documents and self.document_ids only through one helper so they cannot diverge
- Return Option<usize> from index lookups instead of expecting
- Add a debug assertion that the map keys equal the Vec ids after open/close/reorder messages
When it happens
Trigger: A close/remove path deletes from self.documents but forgets self.document_ids (or vice versa), then any operation needing document ordering (tab order, activation after close) calls document_index on the orphaned id.
Common situations: New document-close or session-restore code paths updating only one of the two collections; undo of document open; bugs in reordering logic dropping ids.
Related errors
- Ungrouped folder must have a parent
- Solidify Stroke node should exist
- In `check_layer()`: there should be a `target`
- Artboard should have a primary input
- Merge node
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/539331ac7f56c182.
Report an issue: GitHub.