{"slug": "why-osfoundry-makes-claude-code-name-the-commit-when-it-asks-to-rerun-a-gitnest", "title": "Why osFoundry makes Claude Code name the commit when it asks to rerun a GitNest check", "summary": "OsFoundry's GitNest MCP endpoint requires Claude Code to supply six fields—including an expected change-set version and composition OID—when proposing a check rerun, so approval targets the exact commit the reviewer saw rather than a recomposed change set. A simulation script, checks_sim.py, shows that common model shortcuts for judging check status are wrong in 5 of 7 cases for a no-failure heuristic and 1 of 7 for a passed-equals-total ratio, while a port of GitNest's summarise function handles empty, queued, timed-out and unknown statuses correctly.", "body_md": "This is the tool Claude Code calls when it wants a failed GitNest check run again, as [osFoundry](https://osfoundry.io)'s live MCP endpoint lists it in `tools/list` (no token needed for the catalogue):\n\n```\n{\n  \"name\": \"propose_gitnest_check_rerun\",\n  \"description\": \"Ask the repository owner to retry one finished check on a specific composed change-set version. Approval rechecks repository access, version, commit and check state.\",\n  \"inputSchema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"repository_id\": {\"type\": \"string\"},\n      \"change_set_id\": {\"type\": \"string\"},\n      \"check_name\": {\"type\": \"string\", \"maxLength\": 200},\n      \"expected_change_set_version\": {\"type\": \"integer\", \"minimum\": 1},\n      \"expected_composition_oid\": {\"type\": \"string\"},\n      \"idempotency_key\": {\"type\": \"string\", \"minLength\": 8, \"maxLength\": 128}\n    },\n    \"required\": [\"repository_id\", \"change_set_id\", \"check_name\",\n                 \"expected_change_set_version\", \"expected_composition_oid\", \"idempotency_key\"],\n    \"additionalProperties\": false\n  },\n  \"annotations\": {\"readOnlyHint\": false, \"openWorldHint\": false, \"destructiveHint\": true},\n  \"securitySchemes\": [{\"type\": \"oauth2\", \"scopes\": [\"osfoundry:write\"]}]\n}\n```\n\nIts read-side partner takes two arguments: `list_gitnest_checks {repository_id, change_set_id}`, \"bounded check status for the current composed commit of a change set\". Two more reads feed the decision: `list_gitnest_saved_results` (limit 1–50) and `read_gitnest_work` (`kind` is `tasks`, `changes` or `runs`). All three are `osfoundry:read` with `readOnlyHint: true`.\n\nSix required fields to rerun one check is a lot. A CI button needs a job ID. The extra two, a version and a commit, are what this post is about. No `claude` binary is installed on this Mac, so `checks_sim.py` plays the client. The code and its full output are in `code/`.\n\nBefore an agent proposes anything, it has to read the check list correctly. osFoundry's GitNest UI summarises a commit's check runs with one small pure function (`summarise` in `check-runs.ts`). Its order of tests is the rule:\n\n`none`, never `passed`.` failure`, `timed_out` or `cancelled` is `failed`.` success` keeps it `running`, including a status the client doesn't recognise.\nThe script ports that rule and compares it with two shortcuts a model writes on its own:\n\n```\n== verdict ==\ncase             summarise          no-failure  ratio \nno checks        none               green       green \nall passed       passed 2           green       green \none queued       running 1/2        green       red   \none failed       failed: e2e        red         red   \none timed out    failed: e2e        green       red   \none cancelled    failed: e2e        green       red   \nunknown status   running 1/2        green       red   \nshortcut wrong: no-failure 5/7, ratio 1/7\n```\n\n\"Nothing failed, so it's green\" is wrong in 5 of 7 cases. It calls an unexamined commit green, a queued check green and a timeout green. The ratio check (`passed == total`) is closer, but on zero checks 0 == 0 and it says green too. That's the case the GitNest code comments call the most misleading one, since a commit with no checks hasn't been verified at all. The UI renders it as \"No checks have run against this commit\", with no tick.\n\nIf you prompt Claude Code to answer \"is it green?\", give it these four rules in `CLAUDE.md`. The tool returns statuses, not a verdict.\n\nBecause the person approving it is looking at a specific commit, and the change set can move before they click.\n\nA change set in GitNest is composed from saved results into one commit. Recompose it, after a new saved result or a moved base, and it gets a new version and a new composed commit, and its checks start over. A rerun proposal waits for a human. If it only said \"rerun e2e on cs_4e81a0\", approval would rerun e2e on whatever the change set is by then, which may be a commit the owner never saw.\n\n`checks_sim.py` models that wait. Each trial is one proposal. The agent reads version 3, commit A, with e2e failed. The owner's review time is log-normal with a median of 8 minutes. That's an assumption, not a measurement. During the wait, the change set is recomposed as a Poisson process, on average every 45 minutes, and after a recompose the checks take 4 minutes to run. In 10% of trials a second Claude Code session read the same failure and filed its own proposal with its own key, and the owner approves both. Three approval-time guards, 10,000 trials each:\n\n```\n== approval-time guards (n=10000, review median 8 min, recompose mean every 45 min, 10% second session) ==\nguard          ran on reviewed commit ran on unreviewed commit            duplicate run          refused (stale)   refused (not finished)\nnone                             7789                     1411                     1764                        0                        0\nversion+oid                      7789                        0                      740                     2435                        0\nfull                             7789                        0                        0                     2435                      740\n```\n\nWith no guard, 1,411 approvals reran a check on a commit the owner hadn't reviewed, and 1,764 queued a check that was already queued or running. Comparing version and commit at approval drops the unreviewed runs to 0 and refuses 2,435 stale cards instead. The 740 duplicates left are the second sessions: same version, same commit, but the first approval already requeued the check. The third guard, \"is this check still finished?\", catches those. That's the full set the tool description lists: \"Approval rechecks repository access, version, commit and check state.\"\n\nThe recompose rate is the input I'm least sure of, so here's the no-guard row at three rates:\n\n```\n== sensitivity: unreviewed-commit runs with guard 'none' ==\nrecompose every  15 min  unreviewed  2449  duplicates 3128\nrecompose every  45 min  unreviewed  1411  duplicates 1764\nrecompose every 180 min  unreviewed   452  duplicates 1208\n```\n\nEven a change set that moves every three hours gives 452 wrong-commit runs per 10,000. The guard costs nothing on the happy path: the reviewed-commit count is 7,789 in all three rows.\n\nIn this model they always move together, so either alone would score the same. They answer different questions, though. The version is the change set's own counter, and it moves when its contents change. The commit is what the checks actually ran on, and it's what the card prints as \"Reviewed commit: …\". Asking the client to send both means the server can refuse on either, and the owner reads a commit hash on the card, not an internal counter. The schema enforces the version as an integer ≥ 1. The commit is a free string, so its check happens on the server.\n\nThe script validates arguments against the live `inputSchema` with `jsonschema`:\n\n```\n== propose_gitnest_check_rerun arguments ==\nok                 PASS\nno oid             REJECT  'expected_composition_oid' is a required property\nversion 0          REJECT  0 is less than the minimum of 1\nversion as string  REJECT  '3' is not of type 'integer'\ntwo checks         REJECT  ['e2e', 'lint'] is not of type 'string'\ncheck 201 chars    REJECT  'ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\nkey 7 chars        REJECT  'abcdefg' is too short\nextra 'force'      REJECT  Additional properties are not allowed ('force' was unexpected)\nrequired: repository_id, change_set_id, check_name, expected_change_set_version, expected_composition_oid, idempotency_key\n```\n\nTwo rejections are design, not hygiene. `check_name` is a string, not a list, so \"rerun everything\" is one card per check and the owner decides each one. And `additionalProperties: false` means a model that invents `\"force\": true` gets an error instead of a silently ignored flag.\n\nThe idempotency key should come from what the proposal is about, not from the attempt. The script uses `\"rerun-\" + sha256(change_set_id, version, oid, check_name)[:32]`:\n\n```\nkey retry same: True   key after recompose same: False   len 38\n```\n\nA retry after a lost response sends the same key and doesn't make a second card. A recompose changes the key, so a proposal for the new commit is a new card, which is right.\n\n```\n== tools/list ==\n59 tools, 48140 B via json.dumps, 12722 tok (cl100k)\nlist_gitnest_checks              612 B  158 tok  readOnly=True  destructive=False scope=osfoundry:read\nlist_gitnest_saved_results       707 B  182 tok  readOnly=True  destructive=False scope=osfoundry:read\nread_gitnest_work                685 B  189 tok  readOnly=True  destructive=False scope=osfoundry:read\npropose_gitnest_check_rerun     1011 B  268 tok  readOnly=False destructive=True  scope=osfoundry:write\nall 13 gitnest tools 11227 B 3009 tok\n```\n\nThe 13 GitNest tools are about a quarter of the catalogue's tokens (counted with tiktoken `cl100k_base`, so treat them as approximate). Ten unauthenticated calls each from this Mac in Japan to the live endpoint:\n\n``` bash\n$ python3 probe.py\ninitialize                       HTTP 200     308 B  median    566 ms  min   326  max   807\n  capabilities: {\"tools\": {\"listChanged\": false}}\ntools/list                       HTTP 200   45368 B  median    722 ms  min   420  max  1406\ntools/call list_gitnest_checks   HTTP 401     337 B  median    552 ms  min   409  max   903\n```\n\nThe 401 carries `error_description=\"Connect osFoundry to continue\"` and a `resource_metadata` URL, which is what sends Claude Code into its OAuth flow. `initialize` advertises no push, so nothing tells the client the retry was approved or that the check finished. Waiting for a check is a runner's work on your machine plus a human's click, minutes rather than milliseconds. The cheap pattern is to end the turn after proposing and call `list_gitnest_checks` once when the user says they've approved.\n\nNot osFoundry. GitNest's runner panel says checks \"run on your machine, not ours\". A registered runner polls with its own token (`OSF_RUNNER_TOKEN=… osfoundry-runner --api https://api.osfoundry.io`), and without one checks \"stay queued\". Approving a retry only requeues a check. Claude Code never holds the runner token, and the read tools leave out local bindings and credentials. The agent can see that e2e failed. It can't make anything execute without you and your runner.\n\nThe change-set review page with the checks panel is on the [GitNest page](https://osfoundry.io/platform/gitnest). For the protocol itself, see the [MCP glossary entry](https://osfoundry.io/glossary/mcp).", "url": "https://wpnews.pro/news/why-osfoundry-makes-claude-code-name-the-commit-when-it-asks-to-rerun-a-gitnest", "canonical_source": "https://dev.to/bbt_c84feb0f535/why-osfoundry-makes-claude-code-name-the-commit-when-it-asks-to-rerun-a-gitnest-check-36e1", "published_at": "2026-11-11 10:11:57+00:00", "updated_at": "2026-11-11 10:24:20.243247+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "developer-tools"], "entities": ["osFoundry", "GitNest", "Claude Code", "checks_sim.py", "check-runs.ts"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/why-osfoundry-makes-claude-code-name-the-commit-when-it-asks-to-rerun-a-gitnest", "markdown": "https://wpnews.pro/news/why-osfoundry-makes-claude-code-name-the-commit-when-it-asks-to-rerun-a-gitnest.md", "text": "https://wpnews.pro/news/why-osfoundry-makes-claude-code-name-the-commit-when-it-asks-to-rerun-a-gitnest.txt", "jsonld": "https://wpnews.pro/news/why-osfoundry-makes-claude-code-name-the-commit-when-it-asks-to-rerun-a-gitnest.jsonld"}}