feat(mcp): lock-elected single-writer file watcher (oxcode_watch); release 0.4.0 - #30
Merged
Merged
Conversation
Replace the manual oxcode_index tool with oxcode_watch. Across many `oxcode mcp` processes pointed at one folder, a `.oxcode/watch.lock` advisory file lock (std `File::try_lock`) elects exactly one writer that runs a debounced recursive file watcher and re-indexes on change; the rest serve reads, and a standby takes over via a failover poll if the writer exits (the kernel frees the lock on process death). - oxcode_watch (taskSupport optional) acquires the lock, builds the index, and watches; lock-losers become standbys running a failover loop. - index_for is role-aware: the writer caches + evicts its reader on each reindex, every other process opens fresh per query (so it observes the writer's latest commit); a query with no index returns a hint to call oxcode_watch — queries never write. - oxcode_status reports this instance's role/watching/reindexes; watcher intervals via OXCODE_WATCH_DEBOUNCE_MS / OXCODE_WATCH_POLL_MS. - Remove the --no-watch flag and the startup warm-build. - tests/multiprocess.rs: real 3-process end-to-end test proving single-writer election, reader reads, a single reindexer, and failover. Bump oxcode-model/core/cli to 0.4.0 (inter-crate pins + Cargo.lock); the npm launcher (npm/package.json) and MCP Registry manifest (server.json) to 0.4.0; refresh the Claude plugin description (oxcode_index -> oxcode_watch).
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.
What & why
oxcode mcpis run by many agent processes at once, started and torn down atany time, often pointed at one repo. The old model (a watcher started at server
launch, queries auto-building the index) meant every process would redundantly
re-index the same
.oxcode/index.oxgdband collide on writes.This replaces the manual
oxcode_indextool withoxcode_watch, and elects asingle writer per folder via a cross-process file lock so exactly one process
watches and re-indexes while the rest serve reads.
How it works
oxcode_watch(optionalpath,taskSupport: "optional") opens<root>/.oxcode/watch.lockandtry_lock()s it (std::fs::File::try_lock—advisory
flock, no new dependency; mirrors oxgraph-db's ownWriterLock).watcher, re-indexes on change.
WouldBlock→ standby: serves reads and runs a failover poll; when thewriter process exits the kernel frees the lock and a standby takes over.
every other process opens fresh per query (so it sees the writer's latest
committed snapshot). A query before any
oxcode_watch, with no index, returns aclear "call
oxcode_watch" hint — queries never write, keeping thesingle-writer invariant airtight.
--no-watchflag and the startup warm-build.The election is not a consensus protocol — the kernel is the arbiter. At most
one process holds the exclusive lock at any instant (so no split-brain, even
during failover), and the lock auto-releases on process death (so no lease
renewal or stale-lock cleanup). Scope is same-machine, which fits the use case.
Observability
oxcode_statusnow reports this instance'srole(writer/standby/reader),watching, andreindexescount.oxcode_watchreturns its electedrole.elected as writer,re-indexed (#n),promoted to writer…); stdout stays the JSON-RPC channel.OXCODE_WATCH_DEBOUNCE_MS/OXCODE_WATCH_POLL_MS.Testing
src/mcp.rs): a sub-msflockexclusivity probe,writer election, standby, "query without watch errors", and a real
watcher-reindex test verified by an exact-named search match + the
reindexescounter (which caught — and fixed — a query-echo false positive the previous
watcher test masked).
tests/multiprocess.rs): spawns 3 actualoxcode mcpprocesses driven over stdio via rmcp'sTokioChildProcess, allon one repo. Asserts — entirely over MCP — single-writer election, all three
reading, only the writer's reindex counter advancing while standbys stay 0,
all three seeing the change, and failover: it tears down the writer and
verifies a standby is promoted and re-indexes a fresh edit.
just cigreen (fmt, taplo, clippy-D warnings,cargo deny, all suites).Version bumps (0.4.0)
Feature + breaking MCP-surface change on a 0.x project → minor bump, aligned:
oxcode-model/oxcode-core/oxcode-cli:0.3.0 → 0.4.0(inter-crate pins +Cargo.lock)npm/package.jsonandserver.json(MCP Registry):0.2.0 → 0.4.0(CI also stamps these from the crate version at release)claude-plugin/.claude-plugin/plugin.json: already0.4.0; refreshed its stale description (oxcode_index→oxcode_watch)🤖 Generated with Claude Code