Repository navigation
Conversation
…fferent source The MCP server (one per Claude Code session), the companion and the CLI start independently, so one can run code from before a pull, checkout or edit while another runs the new code against the same catalogs. These fail until: - uncommitted edits are part of the revision (two edit states on one commit differ) - the companion's owner record names its revision - an MCP tool refuses to run against a companion on another revision, or one that records none - the companion refuses a notify from another revision, or one that sends none - the MCP and CLI notifies carry their revision - BuckarooManager.start refuses a Buckaroo server whose version is not the companion's buckaroo - over stdio, the refusal is a tool error carrying the message Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fferent source - The revision names uncommitted edits: a dirty checkout is `<sha>-dirty.<hash of git diff HEAD>`, so two processes on one commit with different edits differ. vite.config.ts bakes the same string into the SPA. - The companion writes its revision and checkout into the owner record of the data dir it claims. - Every MCP tool checks that record before it runs and raises a ToolError naming both sides, which checkout has moved on since its process started, and how to restart each. With no companion on the data dir there is nothing to check. - The MCP and CLI notifies carry their revision. The companion refuses (409, logged as an error) a notify from another revision or with none, which is how an MCP server or CLI from before this check is noticed. - BuckarooManager.start stops the Buckaroo server and raises BuckarooUnavailable when /health reports another buckaroo version than the one the companion runs in-process: a venv synced under a running companion, seen on a respawn. - Tests that post /internal/notify send the revision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
paddymul
marked this pull request as ready for review
September 29, 2026 18:25
…laries_catalog.py f074726 left three E501 lines, so `ruff check` has failed on main since, and CI skips the test jobs behind it. Two tooltip dicts go one key per line; the recipe line splits into two adjacent string literals, which the parser joins back into the same string. The module's AST is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t' into feat/revision-check
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.
Problem
The MCP server (
tallyman mcp, one per Claude Code session), the companion (tallyman run, one per data dir) and the CLI start independently. One can run code from before a pull, a checkout or an uncommitted edit while another runs the new code, all against the same catalogs. #132 made each one report itsgit describe --dirtyrevision, but nothing compares them, and-dirtynames every set of uncommitted edits the same.On this machine an MCP server started from the old
/Users/paddy/tallymanclone at 22f6a3e (before #189) was running against the same~/.tallyman-notebooksas a companion on 4996808. Nothing flagged it.Fix
version.checkout_revision(root)isgit describe --always --dirty --abbrev=7. For a dirty tree it appends the first 6 hex digits of the sha1 ofgit diff --no-ext-diff --no-color --binary HEAD, giving<sha>-dirty.<hash>. Two processes on one commit with different edits now differ.git_revision()still pins it once per process.packages/app/vite.config.tscomputes the same string, so the SPA's existing drift warning still matches. After a build, the bundle had97b9805-dirty.f3c30d, the same string Python reported.claim_data_dirwritesrevisionandsource(the checkout path) intoserver.lock._with_source_check, outside the checkpoint wrapper. Before the tool runs it reads the owner record. If a companion holds the data dir andis_this_revision(owner["revision"])is false (a different revision, a missing one, orunknown), it raisesfastmcp.exceptions.ToolError./internal/notifynow takes arevisionand returns 409, logged at error level, when it differs or is absent. This is the only way the companion notices an MCP server or CLI from before this change, since those have no check of their own. The MCP_notifyand the CLIreset-tosend it. The SSE event leaves it out.BuckarooManager.start()compares theversionfrom/healthwithbuckaroo.__version__in the companion process. On a mismatch it stops the server, logs an error and raisesBuckarooUnavailable. This happens when the venv is synced to another buckaroo under a running companion and the server is then respawned.Uncommitted edits count. An MCP server started before an edit will refuse every tool once the companion is restarted after that edit, until
/mcp→ tallyman → Reconnect.A refusal from the end-to-end run below:
Tests
test_uncommitted_edits_change_the_revision(ImportError)test_the_server_record_names_the_revision_it_runs(KeyError)test_mcp_tools_refuse_to_run_against_a_companion_on_another_revision(DID NOT RAISE)test_mcp_tools_refuse_a_companion_that_records_no_revision(DID NOT RAISE)test_companion_refuses_a_notify_from_another_revision(200, not 409)test_mcp_notifies_and_links_the_companion_of_its_own_data_dir(payload withoutrevision)test_cli_reset_notifies_the_companion_of_its_own_data_dir(KeyError)test_integration_start_refuses_a_buckaroo_other_than_the_companionstest_stdio_tool_call_against_a_companion_on_another_revision_is_a_tool_error/internal/notifyto send the revision, and one exact-payload assertion.main(4e505b8, including the buckaroo 0.15.9 bump) is merged in. Main's own lint has failed since f074726, so this branch also carries fix(scripts): wrap the three lines over 120 columns in rebuild_nfl_salaries_catalog.py #302's one commit (204dcae) so that CI's lint passes and the test jobs run. That commit leaves this diff once fix(scripts): wrap the three lines over 120 columns in rebuild_nfl_salaries_catalog.py #302 is merged.test_fouc.py::test_unknown_api_path_404s: 503 because this worktree had no builtpackages/app/dist. It passes afterpnpm build.uvx ruff checkis clean.~/.tallyman-notebooksand :7860 untouched):97b9805-dirty.f3c30dand the checkout. Buckaroo passed the version check and started.project_listover stdio returned normally.project_listcame back as the tool error above.tallyman reset-to 0from the 4996808 checkout. The CLI printedthe companion at http://127.0.0.1:17901 refused the reload: notify refused: the client runs no revision ..., and the companion logged the refusal.Found along the way, not fixed here
test_reset_to_revision.pyandtest_recalc_surfaces.pyfail locally in about 2 of 5 runs, on main as well (checked against 719a3e8)._git/_stepshelpers fork git throughsubprocess.run.pthread_atforkchild handler:SQLiteHandleCacheclear →sqlite3Close→sqlite3_log→os_log. This is in the macOS crash reports.run_gitusesposix_spawn.git_util.run_git.catalog_listover stdio. It fails fastmcp's client-side output-schema validation: it is annotatedlist, but_tag_projectreturns a dict..mcp.json. It setscwdto/Users/paddy/tallyman, the old clone.🤖 Generated with Claude Code