965a548788
Two follow-ups to the off-loop move, from external review (both verified, the second larger than reported): - Config read-modify-write handlers moved to worker threads could now interleave — _CONFIG_LOCK covers each load/save individually, never the span between them; the event loop used to serialize these accidentally. New _CONFIG_MUTATION_LOCK (worker-threads only, so it can never block the loop) held across the whole load→mutate→save span in all seven RMW handlers. update_config_raw skipped: it's a full-document replace with no server-side read, so a lock cannot close its client-side window. - The review flagged two skills routes still taking _SKILLS_PROFILE_LOCK on the event loop; a systematic audit of hermes_cli/web_routers/ found 24 on-loop routes (skills 5, mcp 9, tools 10, cron 1). All moved to the same inner-_run + asyncio.to_thread pattern, mutating ones under the mutation lock, uniform lock order (_SKILLS_PROFILE_LOCK → _CONFIG_MUTATION_LOCK). Await-safe _config_profile_scope routes, plain def routes, and already-threaded routes unchanged. Regression tests: concurrent theme+font updates both survive (fails with the lock nulled: "theme write lost to a concurrent font write"); event loop stays responsive while the profile lock is held during GET /api/skills. 214 tests passing across the touched suites. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>