personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix audit issues from concurrency safety refactor

- Remove aka from facet relationship writes; aka is identity-level
and belongs only on the journal entity
- Move load_journal_entity to top-level import
- Remove dead update_entity_description function and its export
- Clean up unused now_ms import in saving.py
- Add FileNotFoundError to non-retry exceptions in todo locked_modify

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+1 -54
+1 -1
apps/todos/todo.py
··· 227 227 return modify_fn(checklist) 228 228 finally: 229 229 fcntl.flock(lock_file, fcntl.LOCK_UN) 230 - except (IndexError, TodoError): 230 + except (IndexError, TodoError, FileNotFoundError): 231 231 raise # Logical errors — don't retry 232 232 except OSError as exc: 233 233 last_error = exc
-53
think/entities/saving.py
··· 6 6 This module handles saving entities to storage: 7 7 - save_entities: Save attached or detected entities for a facet 8 8 - save_detected_entity: Concurrency-safe single entity detection with file locking 9 - - update_entity_description: Update a single entity's description with guard 10 9 """ 11 10 12 11 import fcntl ··· 18 17 from think.entities.journal import get_or_create_journal_entity, save_journal_entity 19 18 from think.entities.loading import detected_entities_path, load_entities 20 19 from think.entities.relationships import save_facet_relationship 21 - from think.utils import now_ms 22 20 23 21 24 22 def _save_entities_detected(facet: str, entities: list[EntityDict], day: str) -> None: ··· 287 285 288 286 result = _locked_modify_detected(facet, day, _update_entity) 289 287 return next(e for e in result if e.get("name") == name) 290 - 291 - 292 - def update_entity_description( 293 - facet: str, 294 - name: str, 295 - old_description: str, 296 - new_description: str, 297 - day: str | None = None, 298 - ) -> EntityDict: 299 - """Update an entity's description after validating current state. 300 - 301 - Sets updated_at timestamp to current time on successful update. 302 - 303 - Args: 304 - facet: Facet name 305 - name: Entity name to match (unique within facet) 306 - old_description: Current description (guard - must match) 307 - new_description: New description to set 308 - day: Optional day for detected entities 309 - 310 - Returns: 311 - The updated entity dict 312 - 313 - Raises: 314 - ValueError: If entity not found or guard mismatch 315 - """ 316 - # Load ALL entities including detached/blocked to avoid data loss on save 317 - # For attached entities (day=None), we need include_detached=True and include_blocked=True 318 - entities = ( 319 - load_entities(facet, day, include_detached=True, include_blocked=True) 320 - if day is None 321 - else load_entities(facet, day) 322 - ) 323 - 324 - for entity in entities: 325 - # Skip detached entities when searching 326 - if entity.get("detached"): 327 - continue 328 - if entity.get("name") == name: 329 - current_desc = entity.get("description", "") 330 - if current_desc != old_description: 331 - raise ValueError( 332 - f"Description mismatch for '{name}': expected '{old_description}', " 333 - f"found '{current_desc}'" 334 - ) 335 - entity["description"] = new_description 336 - entity["updated_at"] = now_ms() 337 - save_entities(facet, entities, day) 338 - return entity 339 - 340 - raise ValueError(f"Entity '{name}' not found in facet '{facet}'")