Skip to content

docs(admission): ADR-1125, a cancelled tool call's worker runs to completion (#728, slice 5) - #946

Merged
github-actions[bot] merged 2 commits into
mainfrom
fix/728-cancellation-boundary-adr
Nov 7, 2026
Merged

github-actions[bot] merged 2 commits into
mainfrom
fix/728-cancellation-boundary-adr

Conversation

@cdeust

@cdeust cdeust commented Nov 7, 2026

Copy link
Copy Markdown
Owner

Summary

Last slice of #728. The issue's first acceptance item reads "Keep work counted until completion or a documented cooperative cancellation boundary". Slices 1-4 (#885, #903, #902, #936, #943) implemented and proved the first branch: run_admitted releases the admission slot from the worker's done callback, the caller awaits through asyncio.shield, abandoned workers are reported in memory_stats.abandoned_workers, and real MCP cancel/disconnect is exercised with the real remember on an isolated Cortex store. Nothing forwards the caller's cancellation into the worker thread, so no cooperative boundary exists.

ADR-1125 records these semantics as the decision: no cooperative cancellation boundary; the slot belongs to the worker (amends ADR-0329); abandoned work is reported, not hidden; a handler that wants to stop early must amend the ADR and specify the consistent stopping point, how cancellation reaches the thread, that the slot is kept until return, and the abandoned_work outcome. Out of scope: safe_handler without tool_name (no admission).

Root cause addressed by the series: the slot was owned by the caller instead of the worker. This PR adds no code.

Closes #728

Type of change

  • Documentation only

Test plan

  • tests_py/infrastructure/test_project_wiki_mirror.py, tests_py/handlers/test_admission_cancellation.py, tests_py/server/test_safe_handler_cancellation.py, tests_py/handlers/test_abandoned_work_status.py, tests_py/handlers/test_memory_stats_mapping.py: 51 passed.
  • tests_py/handlers/test_project_wiki_tools.py, tests_py/scripts/test_craftsmanship_decisions.py, tests_py/scripts/test_migrate_decision_blocks.py: 52 passed.
  • wiki_decision_mirror.check_mirror(.) returns [] after generate_mirror; python scripts/check_craftsmanship.py: OK.
  • No new test: the ADR cites the existing tests that hold each decision point.

Audit notes

  • Not verified: the full suite (installed without torch, its download host is blocked in this environment); the ruff available here (not the lock-pinned one) reports E501 on files this PR does not touch, so lint is left to CI.

Reviewer checklist

  • CHANGELOG.md updated under the appropriate section.
  • Documentation updated (docs/adr mirror + wiki/adr canonical page + manifest).
  • No secrets / credentials / PII in the diff.

Generated by Claude Code

…pletion (#728, slice 5)

Issue #728 asks to keep work counted "until completion or a documented
cooperative cancellation boundary". Slices 1-4 (#885, #903, #902, #936,
#943) implement and prove the first: the slot is released by the worker's
done callback, the caller awaits through asyncio.shield, abandoned workers
are reported in memory_stats.abandoned_workers. Nothing forwards the
cancellation into the worker thread, so no cooperative boundary exists.

ADR-1125 records those semantics, amends ADR-0329 (the slot belongs to the
worker), states what a handler that wants to stop early must specify, and
lists the tests that hold each point. Mirror regenerated with
wiki_decision_mirror.generate_mirror.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYs8nydviS39BjwT88E
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-07T12:33:09.578089Z 8638320 New commits
🔒 Security Review ✅ Completed 2026-11-07T12:34:48.399750Z 8638320 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 2m 19s —— View job


Review of PR #946 done

Result: no Blockers. The review posts 3 Notes that need no action before merge:

  • the cancelled outcome when the event loop shuts down;
  • no test asserts the cancelled counter;
  • PR CI never checks the repo's own mirror.

Not done: I didn't run check_mirror or the tests myself. Running Python wasn't permitted in this job, so I checked the mirror by reading it against mirror_bytes.

Verdict: approve

@claude

claude Bot commented Nov 7, 2026

Copy link
Copy Markdown

Review 1 of 3: PR #946 (ADR-1125, #728 slice 5)

I ran five reviews: code quality, documentation accuracy, security, performance and test coverage. None of them found a Blocker.

I checked each ADR claim against the code. The slot is taken and handed to the worker with no await in between (mcp_server/handlers/admission.py:92-94). It is released only from the worker's done callback. The caller waits through asyncio.shield (:96). All 61 safe_handler( calls in mcp_server/tool_registry_*.py pass a literal tool_name=, so the path without admission (Decision 5) can't be reached through an MCP tool. All six cited test files exist and test what the ADR says they test. The mirror banner and body match mirror_bytes, and the manifest entry is valid and stays inside the wiki root.

Notes, no action before merge

  • Decisions 2 and 3 say the slot is released "when the worker ends". If the to_thread future is itself cancelled (loop teardown), _release_slot and _log_abandoned record cancelled while the thread may still be running (admission.py:94,113; test_admission_cancellation.py:146-163). One sentence in the ADR could say so.
  • No test asserts snapshot()["ended"][...]["cancelled"] == 1. The cancelled outcome is checked only through the log text (tests_py/handlers/test_admission_cancellation.py:146-162).
  • PR CI never runs check_mirror on the repo's own wiki/: check_craftsmanship.py runs only in the dispatch and SQLite-path workflows. So the mirror is held only by the author's local run. This predates the PR.

Verdict: approve

cdeust commented Nov 7, 2026

Copy link
Copy Markdown
Owner Author

Réponse à la revue 1 :

  • Note 1 (teardown de la boucle) : corrigée dans 8638320 (fixup!). Les décisions 2 et 3 nomment désormais l'exception : quand la boucle annule la tâche du worker, le créneau est rendu et l'issue cancelled est enregistrée alors que le thread peut encore tourner et écrire. Le test test_abandoned_worker_cancelled_by_loop_teardown_is_logged est cité. Miroir régénéré, check_mirror vide, 43 tests passés.
  • Note 2 (aucune assertion sur snapshot()["ended"][...]["cancelled"]) : je laisse ce point hors de cette PR, qui ne contient que de la documentation. C'est un trou de test sur le code de feat(admission): count workers still running after their caller was cancelled (#728, slice 2) #903.
  • Note 3 (check_mirror absent de la CI des PR) : antérieur à la PR et ce n'est pas l'objet de celle-ci ; cela touche les workflows, que le token de l'autopilot ne peut pas modifier.

Generated by Claude Code

@claude

claude Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 2m 27s —— View job


Review 2 of 3 of PR #946 is done

Result: no Blockers. Both new claims match mcp_server/handlers/admission.py:94-114. The docs/adr mirror is byte-identical to the banner plus the wiki page. The review posts 3 Notes that need no action before merge:

  • the cited test cancels the worker task by hand and checks only the log line;
  • the Consequences section and Decision 4 don't mention the teardown exception;
  • Decision 3 counts cancelled workers as ended, but their thread may still be running.

Not done: Python wasn't permitted in this job, so I didn't run check_mirror or the tests. I checked the mirror with cmp instead.

Verdict: approve

@claude

claude Bot commented Nov 7, 2026

Copy link
Copy Markdown

Review 2 of 3: PR #946 (ADR-1125, #728 slice 5)

Review 1 (at 58cf52a) found no Blockers, so nothing needed re-checking. This review covers only 8638320, the fixup that adds the loop-teardown exception to Decisions 2 and 3. I ran five reviews on it: code quality, documentation accuracy, security, performance and test coverage. None found a Blocker.

Both new claims match the code. The slot is released from the worker's done callback (mcp_server/handlers/admission.py:94,103). Cancelling the to_thread task runs that callback, but the thread keeps running. cancelled is recorded only when worker.cancelled() is true, on a worker whose caller was already cancelled (admission.py:97-99,113-114). The cited test exists (tests_py/handlers/test_admission_cancellation.py:146). Both ADR files got the same edit, and the docs/adr mirror is byte-identical to the banner plus the wiki page (checked with cmp, since Python was not allowed in this job).

Notes, no action before merge

  • The cited test cancels the worker task by hand (:156) and checks only the log line (:159-162). It never tears down a loop, and it never checks that the slot is free while the thread is still blocked.
  • Some wording still assumes the slot is held until the thread returns. Consequences says "the budget holds under cancellation" and "the slot is kept until the worker ends", with no teardown caveat. Decision 4 says "until the worker actually returns" without saying whether that means the asyncio task or the thread, which the new exception shows are different.
  • Decision 3 says "how the ended ones ended", and that count includes cancelled, but a cancelled worker's thread may still be running.

Verdict: approve

cdeust commented Nov 7, 2026

Copy link
Copy Markdown
Owner Author

Réponse à la revue 2 (notes optionnelles, pas de push pour elles) :

  • Le test cité annule la tâche à la main au lieu de détruire une boucle : c'est exactement ce que fait la destruction de boucle (annuler les tâches en cours), et l'ADR ne lui attribue rien de plus que le comportement du callback. Un test qui vérifie que le créneau est libre pendant que le thread bloque relève du même trou de test que la note 2 de la revue 1 (code de feat(admission): count workers still running after their caller was cancelled (#728, slice 2) #903), hors de cette PR documentaire.
  • Conséquences et décision 4 : la décision 2 porte l'exception pour tout l'ADR ; « until the worker actually returns » vise le thread, ce que l'exception rend explicite. Je ne relance pas un cycle CI/revue pour une reformulation.
  • Décision 3 : la phrase ajoutée juste après précise qu'un cancelled peut encore finir et écrire.

Generated by Claude Code

@github-actions
github-actions Bot merged commit 574a3fb into main Nov 7, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: retain admission ownership while cancelled handler workers remain active

2 participants