{"record":{"id":"c76379ef27220c92","repo":"spacedriveapp/spacedrive","slug":"entry-not-found-after-id-lookup","errorCode":null,"errorMessage":"Entry {} not found after ID lookup","messagePattern":"Entry (.+?) not found after ID lookup","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/src/ops/indexing/change_detection/persistent.rs","lineNumber":166,"sourceCode":"\t\t}\n\n\t\tlet model = q.one(&self.db).await?;\n\t\tOk(model.map(|m| m.id))\n\t}\n}\n\n#[async_trait::async_trait]\nimpl ChangeHandler for DatabaseAdapter {\n\tasync fn find_by_path(&self, path: &Path) -> Result<Option<EntryRef>> {\n\t\tlet entry_id = match self.resolve_entry_id(path).await? {\n\t\t\tSome(id) => id,\n\t\t\tNone => return Ok(None),\n\t\t};\n\n\t\tlet entry = entities::entry::Entity::find_by_id(entry_id)\n\t\t\t.one(&self.db)\n\t\t\t.await?\n\t\t\t.ok_or_else(|| anyhow::anyhow!(\"Entry {} not found after ID lookup\", entry_id))?;\n\n\t\tlet kind = match entry.kind {\n\t\t\t0 => EntryKind::File,\n\t\t\t1 => EntryKind::Directory,\n\t\t\t2 => EntryKind::Symlink,\n\t\t\t_ => EntryKind::File,\n\t\t};\n\n\t\tOk(Some(EntryRef {\n\t\t\tid: entry.id,\n\t\t\tuuid: entry.uuid,\n\t\t\tpath: path.to_path_buf(),\n\t\t\tkind,\n\t\t}))\n\t}\n\n\tasync fn find_by_inode(&self, inode: u64) -> Result<Option<EntryRef>> {\n\t\tlet inode_val = inode as i64;","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/spacedriveapp/spacedrive/blob/6dfeccf2113039e35f2ce735f945e70dc3e4ea45/core/src/ops/indexing/change_detection/persistent.rs#L148-L184","documentation":"find_by_path first resolves a path to an entry id via resolve_entry_id, then loads that id with find_by_id. This error means resolution returned Some(id) but the row is gone by the time the SELECT runs: a delete, a reindex that rewrote rows, or a stale closure/path mapping referencing a dead id. The two-step lookup is inherently racy under concurrent writes.","triggerScenarios":"A watcher event for a path whose entry was just deleted by another worker; reindex rebuilding the entry table between the id resolution and the row fetch; stale entry_closure rows pointing at removed ids.","commonSituations":"Concurrent indexer and watcher pipelines on the same library; a location being reindexed while change events stream in; crashed delete that removed the entry but not closure rows.","solutions":["Treat the missing row as 'not found' (return Ok(None)) so the pipeline recreates the entry instead of failing","Check for concurrent reindex/delete jobs running against the same library","Verify delete paths also clean entry_closure rows so resolution cannot return dead ids"],"exampleFix":"// before\nlet entry = entities::entry::Entity::find_by_id(entry_id)\n    .one(&self.db)\n    .await?\n    .ok_or_else(|| anyhow::anyhow!(\"Entry {} not found after ID lookup\", entry_id))?;\n\n// after: a vanished row is a normal race, not a hard failure\nlet Some(entry) = entities::entry::Entity::find_by_id(entry_id)\n    .one(&self.db)\n    .await?\nelse {\n    return Ok(None);\n};","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// A vanished row after id resolution is a benign race: treat as not-found\nlet entry = match entities::entry::Entity::find_by_id(entry_id).one(&db).await? {\n    Some(e) => e,\n    None => return Ok(None), // caller will recreate the entry\n};","preventionTips":["Avoid running reindex and change-detection concurrently on one library","Keep delete paths consistent: remove entry and its closure rows together","Prefer single-query path lookups over resolve-then-fetch where possible"],"tags":["indexing","database","race-condition","change-detection"],"backgroundTag":null,"analyzedSha":"6dfeccf2113039e35f2ce735f945e70dc3e4ea45","analyzedAt":"2026-08-16T11:26:17.074Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}