odysseus/src
Ashvin c8a012d4d2
Some checks failed
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
ci / docker publish / build (amd64) (push) Has been cancelled
ci / docker publish / build (arm64) (push) Has been cancelled
ci / docker publish / merge manifest + tag (push) Has been cancelled
fix(memory): don't let an unreadable store get overwritten with an empty one (#5831)
* fix(memory): don't let an unreadable store get overwritten with an empty one

load_all() answered a failed read the same way it answered an empty store:
with []. Every mutation path is a read-modify-write (load the whole file,
change it, save it back), so a failed read became

    load_all() -> []  ->  [].append(new)  ->  save([new])

and save() is atomic, so the replacement stuck.

The case that actually destroys data is a store that is READABLE but not
parseable - a truncated file, or one holding {} instead of []. Nothing
obstructs the write, so adding a memory returns HTTP 200 and every memory
already stored is gone. Verified end-to-end against a running instance: on the
current code a truncated memory.json plus one add leaves the file holding only
the new entry. Truncation is reachable - core/database.py rewrites memory.json
during migration with a plain open(.., "w") + json.dump, which is not atomic.

A live exclusive lock is not the dangerous case: it blocks the read and the
os.replace alike, so the save fails too and the store survives. That path
currently 500s and loses nothing.

_read_entries() now returns [] only when the file genuinely does not exist and
raises MemoryStoreUnreadable for every other failure, including a store that
parses but is not a JSON array. load_all() keeps the old lenient behaviour so
display, search and context injection still degrade quietly instead of
breaking chat. The read-modify-write callers switch to load_all_for_update(),
which propagates the error: the memory routes turn it into a 503 and change
nothing, backup import refuses rather than saving only the incoming rows, and
auto-extraction and the audit merge skip the write. The audit merge mattered
most - it rebuilds the whole file from one owner's slice plus everyone else's
rows, so an empty read there dropped every other tenant's memories.

The corrupt-JSON path still gets its one shot at the legacy memory.txt
migration before raising, so that recovery is unchanged.

The two updated fakes gained load_all_for_update because the real class has it;
MagicMock would otherwise hand the import path a Mock instead of the seeded list.

Fixes #5673

* fix(memory): fail closed on the remaining read-modify-write add paths

The strict loader landed with the routes, the backup import and the extractor
converted, but three read-modify-write sinks still called load_all(), which
degrades an unreadable store to []. Two of them are the paths users actually
reach, so the data loss in #5673 stayed reproducible:

- src/ai_interaction.py do_manage_memory, action "add" — reached from ordinary
  chat via src/tool_execution.py:793 -> dispatch_ai_tool. "Remember that I
  prefer X" against an unreadable store wrote a one-entry file over it and
  reported success.
- mcp_servers/memory_server.py, action "add" — the same shape through
  _scope_entries(), registered as a built-in in src/builtin_mcp.py.
- src/memory_provider.py NativeMemoryProvider.remember and .delete — wired
  into app state in src/app_initializer.py but not consumed outside tests yet,
  converted here so the pattern is uniform before it goes live.

The MCP server takes _scope_entries(for_update=True) so list keeps the lenient
read. The edit and delete branches on both tool paths were already fail-closed
by accident — an empty view matches nothing and returns before the save — so
they are left alone.

The three new tests drive the real entry points rather than replaying the
shape, and use a truncated store, which is the case that reads back fine so
nothing stops the save. Each asserts memory.json is byte-identical afterwards;
all three fail on the previous commit with the store overwritten.
2026-08-06 02:33:50 -06:00
..
agent_tools Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
model_capability_readers feat(models): define capability schema and readers (#2739) 2026-07-18 09:40:58 +01:00
search refactor(search): make src analytics a service shim (#2264) 2026-06-04 18:57:24 +02:00
tools fix(skills): require manage_skills action (#5856) 2026-08-04 04:17:45 -06:00
action_intents.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
agent_loop.py fix(agent): import Any for tool event helper (#5735) 2026-07-27 17:29:29 +02:00
agent_runs.py Agent stream: 10s heartbeat keepalive on the SSE subscribe so long-running thinking models dont drop the connection 2026-06-19 00:34:30 +00:00
ai_interaction.py fix(memory): don't let an unreadable store get overwritten with an empty one (#5831) 2026-08-06 02:33:50 -06:00
api_key_manager.py fix: use atomic write in APIKeyManager.save() to prevent credential data loss (#4591) (#4597) 2026-06-23 23:28:53 +02:00
app_helpers.py fix(routes): log and cleanly 500 on unreadable HTML page (#4637) 2026-06-23 16:12:32 +02:00
app_initializer.py fix(stabilization): harden attachment lifecycle and agent guard signals (#5420) 2026-07-11 15:14:14 +01:00
assistant_log.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
attachment_refs.py fix(stabilization): harden attachment lifecycle and agent guard signals (#5420) 2026-07-11 15:14:14 +01:00
auth_helpers.py fix(auth): gate api tokens from user routes (#2992) 2026-06-07 12:55:01 +02:00
bg_jobs.py feat(agent): add manage_bg_jobs tool to inspect and kill background bash jobs (#4577) 2026-06-19 00:28:22 -07:00
bg_monitor.py fix: drop thinking deltas from background agent loops 2026-06-15 15:03:09 +09:00
builtin_actions.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
builtin_mcp.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
caldav_sync.py CalDAV: close the DAVClient on sync and write-back paths (#4793) 2026-07-11 13:03:24 +01:00
caldav_writeback.py CalDAV: close the DAVClient on sync and write-back paths (#4793) 2026-07-11 13:03:24 +01:00
chat_handler.py fix(stabilization): harden attachment lifecycle and agent guard signals (#5420) 2026-07-11 15:14:14 +01:00
chat_helpers.py fix(chat): keep balanced trailing ')' when extracting URLs (#3406) 2026-06-08 21:33:29 +02:00
chat_processor.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
chatgpt_subscription.py Fix model endpoint route test regressions 2026-06-09 10:16:38 +09:00
chroma_client.py fix: ChromaDB unreachable blocks app startup for 30-60s (#326) (#476) 2026-06-01 22:22:41 +09:00
cleanup_service.py Replace cleanup service datetime.utcnow calls (#1494) 2026-06-03 14:14:27 +09:00
config.py fix(cleanup): update MODULE_SUMMARY and remove dead MEMORY_DOC paths (#4411) (#5160) 2026-07-11 17:06:19 +01:00
constants.py Merge branch 'odysseus-dev:main' into main 2026-07-17 16:22:02 -05:00
context_budget.py Ignore invalid context budget numbers (#1831) 2026-06-29 19:56:17 +01:00
context_compactor.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
cookbook_serve_lifecycle.py Polish mobile UI and editor workflows 2026-06-27 13:05:44 +00:00
copilot.py fix(copilot): guard request_flags against a non-dict last message (#5274) 2026-07-08 23:57:23 +02:00
database.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
deep_research.py fix(research): track analyzed URLs separately (#3125) 2026-06-10 12:08:22 +01:00
document_actions.py Merge remote-tracking branch 'origin/dev' 2026-07-01 10:11:22 +00:00
document_processor.py fix(chat): give extensionless image/audio uploads a valid MIME subtype (#5205) 2026-07-08 21:04:15 +02:00
email_thread_parser.py Ignore non-string email thread bodies (#1654) 2026-06-03 14:06:31 +09:00
embedding_lanes.py fix(embeddings): survive numpy embeddings when restoring a reset lane (#3410) 2026-06-09 10:40:17 +02:00
embeddings.py Retry oversized embedding requests (#1106) 2026-06-26 14:21:27 +01:00
endpoint_resolver.py chore: update repository URLs after organization transfer (#5622) 2026-07-20 16:43:47 +02:00
event_bus.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
exceptions.py refactor(exceptions): dedupe src/exceptions via core re-export (#4785) 2026-06-24 16:50:07 +02:00
generated_images.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
goal_based_extractor.py fix(deep-research): wrap fetched webpage content in untrusted-context sandbox 2026-06-06 03:37:10 -06:00
host_docker_access.py fix(docker): make host Docker socket opt-in (#4902) 2026-06-30 19:54:51 +02:00
image_model_ids.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
index_walk.py fix(rag): skip hidden and junk directories when indexing (#5633) 2026-07-23 14:18:08 +02:00
integrations.py fix(integrations): pin api_call to the SSRF-validated IP (#5727) 2026-08-04 04:17:41 -06:00
interactive_gate.py Checkpoint Odysseus local update 2026-07-07 00:50:07 +00:00
llm_core.py fix(llm): omit temperature for major-only Opus ids (claude-opus-5) (#5761) 2026-07-30 11:30:00 +01:00
markitdown_runtime.py Settings overhaul + UI polish pass 2026-06-10 15:15:13 +09:00
mcp_manager.py fix(mcp_manager): remove timeout from MCP connection attempts and handle registration cleanup 2026-07-13 08:56:46 +02:00
mcp_oauth.py fix(mcp): guard DbTokenStorage against non-dict oauth_tokens JSON (#5107) 2026-07-11 05:26:23 +01:00
memory.py fix(memory): don't let an unreadable store get overwritten with an empty one (#5831) 2026-08-06 02:33:50 -06:00
memory_provider.py fix(memory): don't let an unreadable store get overwritten with an empty one (#5831) 2026-08-06 02:33:50 -06:00
memory_vector.py fix: split Chroma embedding lanes (#3046) 2026-06-06 03:17:19 -06:00
model_capabilities.py feat(models): define capability schema and readers (#2739) 2026-07-18 09:40:58 +01:00
model_context.py fix(model-context): read real context window for unknown proxy models (#4909) 2026-06-30 18:04:29 +01:00
model_discovery.py fix(models): accept bare-list /models responses (Together AI) (#4761) 2026-06-27 16:25:15 +01:00
office_doc.py Merge remote-tracking branch 'origin/dev' into test-main-dev-merge-20260615 2026-06-15 21:20:15 +09:00
optional_deps.py fix(image): patch realesrgan torchvision compatibility (#4110) 2026-06-15 15:16:41 +09:00
pdf_form_doc.py refactor(tools): extract document tools to handle registry (#3666) 2026-06-10 10:41:52 +02:00
pdf_forms.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
pdf_runtime.py Show a clear message when PyMuPDF is missing 2026-06-01 18:27:17 +09:00
personal_docs.py fix(rag): skip hidden and junk directories when indexing (#5633) 2026-07-23 14:18:08 +02:00
preset_manager.py fix(presets): persist presets atomically to avoid corruption on crash (#2169) 2026-06-08 19:16:37 +02:00
prompt_security.py Isolate untrusted context from visible user prompts (#3584) 2026-06-27 13:50:04 +01:00
rag_manager.py fix: resolve RAG manager search signature TypeError (#4994) 2026-07-03 15:07:16 +01:00
rag_singleton.py feat(paths): abstract runtime path logic for frozen distribution packages (#969) 2026-06-15 17:44:10 +01:00
rag_vector.py fix(rag): skip hidden and junk directories when indexing (#5633) 2026-07-23 14:18:08 +02:00
rate_limiter.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
readiness.py feat: add /api/ready readiness probe (DB, data dir, local-first) (#1200) 2026-06-02 23:33:22 +09:00
reminder_personas.py Settings overhaul + UI polish pass 2026-06-10 15:15:13 +09:00
request_models.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
research_handler.py fix(research): migrate active task owners on rename (#3618) 2026-06-11 01:17:02 +02:00
research_utils.py Treat non-string research summaries as low quality 2026-06-03 13:42:24 +09:00
runtime_paths.py feat(paths): abstract runtime path logic for frozen distribution packages (#969) 2026-06-15 17:44:10 +01:00
secret_storage.py refactor(constants): single source of truth for data dir (#3368) 2026-06-08 09:58:52 +02:00
service_health.py feat(diagnostics): add consolidated service health endpoint for degraded-state reporting (#964) 2026-06-09 16:00:24 +01:00
session_actions.py fix(sessions): keep fresh chats during auto tidy (#1871) 2026-06-09 01:06:20 +01:00
session_image_cleanup.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
session_search.py Polish mobile UI and editor workflows 2026-06-27 13:05:44 +00:00
settings.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
settings_scrub.py fix(settings): scrub camelCase secret keys (#3707) 2026-06-11 12:53:33 +02:00
task_action_policy.py fix(tasks): gate cookbook serve task execution (#5235) 2026-07-05 13:19:04 +01:00
task_endpoint.py Checkpoint Odysseus local update 2026-07-07 00:50:07 +00:00
task_scheduler.py Merge remote-tracking branch 'origin/dev' 2026-07-07 00:51:34 +00:00
teacher_escalation.py fix(ai): offload model resolution from async paths 2026-06-28 00:48:35 +01:00
text_helpers.py fix(security): prevent ReDoS in LLM-output tool/think parsers (#4704) 2026-06-27 10:12:28 -07:00
tls_overrides.py Support extra CA bundle for private-CA LLM providers (#769) 2026-06-04 13:18:50 +01:00
tool_execution.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
tool_implementations.py Merge dev into main for testing 2026-06-28 14:07:23 +00:00
tool_index.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
tool_parsing.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
tool_policy.py fix(chat): require explicit web search enable 2026-07-12 08:20:59 +02:00
tool_schemas.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
tool_security.py Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
tool_utils.py fix(stabilization): harden attachment lifecycle and agent guard signals (#5420) 2026-07-11 15:14:14 +01:00
topic_analyzer.py Topics: hydrate session history before analysis 2026-06-02 20:44:27 +09:00
upload_handler.py fix(stabilization): harden attachment lifecycle and agent guard signals (#5420) 2026-07-11 15:14:14 +01:00
upload_limits.py refactor(uploads): centralize upload byte-limits in upload_limits.py (#3364) (#3518) 2026-06-09 01:24:30 +02:00
url_safety.py fix(url-safety): reject RFC 6598 shared address space in strict mode (#5474) 2026-07-18 12:36:27 -06:00
url_security.py Reapply "Merge branch 'main' of github.com:pewdiepie-archdaemon/odysseus" 2026-06-03 22:47:00 +09:00
user_time.py fix(tasks): keep scheduled-task prompt cache stable 2026-06-28 00:05:02 +01:00
visual_report.py fix(visual_report): ignore fenced headings in TOC extraction 2026-06-27 17:44:32 +01:00
webhook_manager.py fix(security): pin webhook delivery to the SSRF-validated IP (DNS rebinding) (#5147) 2026-07-04 17:03:38 +01:00
youtube_handler.py fix(youtube): consolidate duplicate handler 2026-06-15 15:03:41 +09:00