Repository navigation
fix(mentions): a corrupt mention_threads.json must not brick server startup - #690
Merged
devikaverma merged 3 commits intoSep 30, 2026
Merged
Conversation
MentionSessionStore._load() is the one JSON store built by SessionManager.__init__ with no corruption guard, so a torn file (the non-atomic _save leaves one behind after a crash, SIGKILL or a full disk) raises JSONDecodeError out of the constructor and openworker-server never starts again until the user finds and deletes the file by hand. A record written before a field was added raises TypeError the same way. Use the guard the neighbouring ParkedStore in the same constructor block already applies.
Covers the valid-file control, a torn document, and a record predating a field addition, plus recovery: the next set() rewrites the unreadable file.
The wrapped comprehension is what `ruff format` produces for the line; the sibling store files (connectors/parked.py, subscriptions.py, inbox.py) are already format-clean.
devikaverma
approved these changes
Sep 30, 2026
devikaverma
left a comment
Collaborator
There was a problem hiding this comment.
LGTM, thanks for redoing this off current main. Agree with keeping the atomic write out of this one.
Two things to follow up on separately, not here:
- A file that's valid JSON but not an object (
[],null) still crashes on.getwith AttributeError, which isn't in the except tuple. - This isn't the last unguarded store. inbox, inbox_routing, subscriptions, unrouted, unattended, connections, overrides and selfwake still load without a guard on main, so the "this one was the outlier" line in the test docstring isn't right.
This was referenced Sep 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #682 — that branch was cut from a stale fork base and shows ~5k lines of unrelated
sandbox/files in its diff. This one is 3 commits off currentmain(5870585),+56/-2,two files:
coworker/mentions.pyand a new test.Problem
MentionSessionStore._load()is the one JSON state store built bySessionManager.__init__thatstill has no corruption guard:
It is constructed unconditionally while the server boots —
coworker/server/run.py:main()→build_app()→SessionManager.__init__→manager.py:421 MentionSessionStore(base / "mention_threads.json")— andmain()wraps that intry: … finally:with noexcept. So one unreadable file raises straight out of the constructorand
openworker-server(also the desktop app's sidecar) never starts again, on every launch, untilthe user guesses the filename and deletes it. #204 lists this store as affected; #158 was the same
failure for
wakes.json.Two independent triggers, both re-executed against the real module on
5870585:The first one is self-inflicted by this store's own
_save(), which writes in place withwrite_text(no temp +os.replace), so a crash,SIGKILLor a full disk mid-write producesexactly the truncated document that then bricks startup. The second is the ordinary consequence of
adding a field to
MentionThread: every existing install's file stops loading.Fix
Guard the load the way its immediate neighbour in the same constructor block already does — this is
coworker/connectors/parked.py:49-55verbatim in shape, including the comment:Deliberately kept to the read side:
SubscriptionStore's buffer, the people directory,ParkedStoreandConversationStore._read_jsonlall made this same call, so the contract in thisrepo is already "start, and let the next
_save()overwrite the unreadable file". The test belowcovers that recovery step.
I did not change
_save()to write atomically, even though #204 also asks for that: it is aseparate failure mode (it creates the corruption instead of surviving it), #70 already landed the
tmp-then-
os.replacepattern for the path that needed it most, and bundling it here would make thisdiff two behaviour changes instead of one. Happy to add it as a follow-up if you would rather have
both.
I also scoped this to one store rather than to #204's whole table on purpose: #205 proposed a shared
coworker/jsonstore.pyhelper for all of them and was closed unmerged, so this PR follows thepiecemeal shape the repo has actually been using.
mentions.pyis the entry in that table that isstill unguarded and on the boot path.
Tests
New
tests/test_mention_store_robustness.py, in the shape oftests/test_conversation_jsonl_robustness.py:test_load_survives_a_torn_thread_file— truncated document starts empty, and the nextset()makes the file readable again for a fresh store instance.
test_load_survives_a_record_with_a_missing_field— a record predatingchannelno longer raises.test_load_reads_a_valid_file— control: the normal path is untouched.Adjacent suites on this branch:
pytest tests/test_mention_router.py tests/test_mention_store_robustness.py tests/test_conversation_jsonl_robustness.py -q→17 passed in 10.57s.grep -l mention tests/*.py, 19 files):10 failed, 301 passed in 244.01s. Those 10 are pre-existing on Windows and not caused by thischange: re-running exactly those 10 node ids with
5870585'scoworker/mentions.py(patchreverted) gives
10 failed, 14 passed in 20.48s— the same ids (tests/test_slack_relay.pywebsocket timeouts ×6,
tests/test_security_bundles.py×2,tests/test_configured_spawn.py×1,tests/test_github_installs.py×1).ruff checkandruff format --check(ruff 0.15.17) are clean on both files; the wrappedcomprehension above is
ruff format's output.Local env: CPython 3.12.11 on Windows, pytest 9.1.1. CI runs
pytest tests -q --cov=coworkeronLinux, which is where those 10 should be green — I could not get the whole suite to finish here (it
stalls past ~84% in the connector/browser modules), so the numbers above are the scoped runs I did
complete, not the full gate.
Per the README's "attach what was broken and how it is fixed now": this is a headless boot-time
crash with no UI surface, so the before/after evidence is the two tracebacks and the red→green test
output rather than screenshots.