Skip to content

feat(runtime-cli): support idempotency keys in task submit - #5595

Merged
keweichen merged 2 commits into
mainfrom
factory/kc/revalidate-ca5c94bedffe4d950f98987a-ffd97d25ee72
Oct 16, 2026
Merged

keweichen merged 2 commits into
mainfrom
factory/kc/revalidate-ca5c94bedffe4d950f98987a-ffd97d25ee72

Conversation

@keweichen

Copy link
Copy Markdown
Collaborator

Priority

Level: medium
Reason: Owner-approved reliability improvement.

Closes

N/A — owner-approved work; no public issue is claimed as closed.

Summary

feat(runtime-cli): support idempotency keys in task submit

Review first: src/runtime-cli/sutando-runtime.py, tests/runtime-cli-task-idempotency.test.py.

Checklist

  • Confirmed no other open PR closes the same issue (gh pr list --repo sonichi/sutando --search "closes #N")
  • Git author email matches my CLA-signed email (git log -1 --format='%ae' shows a GH-mapped email, not *.local or noreply@anthropic.com)
  • Single concern per PR — no bundled refactors / drive-by feature additions
  • Confirmed bug exists on upstream/main (or feature isn't already covered)
  • Test added (or N/A explained below)
  • Before/after evidence pasted below — the actual command output for both states, not a description of it (this is the Add demo video to README #1 reason PRs bounce)
  • Every claim in this PR body is verifiable from the diff or the pasted output — no statement the reviewer can't check
  • Live path? If this touches a bridge, network path, delivery loop, or startup, I included a real post-restart round-trip — unit tests / harnesses alone are not accepted for these
  • Stacked PR? I named the parent + merge order and will rebase/update + rerun full checks after the parent lands (or N/A)
  • Scanned added lines for host-specific hardcoded paths and inline home/workspace fallbacks; fixtures are narrowly scoped
  • If this PR adds or edits a CI workflow, I confirmed it actually runs on this PR (see the Checks tab — a branches: filter can silently exclude a stacked PR)
  • Doesn't reinvent workspace / home-path resolution — uses the resolve_workspace / resolveWorkspace / claude_home_path helpers (see CLAUDE.md "Workspace contract"; enforced by the lint-workspace-resolution, lint-claude-home-path, and workspace-leak-check CI checks)

Contribution observations (unchecked items remain reviewer decisions):

These observations used the isolated candidate; no production deployment was performed.

  • Risks / rollback: Change scope: Add optional --idempotency-key to task submit and forward its supplied value as idempotencyKey to the existing RPC. Omit the parameter when the option is absent. Document its retry semantics in CLI help and add focused CLI integration tests against the real Unix-socket daemon. Preserve existing backend validation and unkeyed behavior. Exclude automatic retries, interactive chat changes, reconnect behavior and new deduplication machinery.
    Rollback: revert this PR. Production behavior after deployment remains to be observed.
  • Stacking: N/A; one concern against main.
  • Author maps to keweichen; CLA status must be observed on the PR.

Before / after evidence

Before

  • triage-evidence: Verified the exact pinned HEAD and clean working tree before and after observation. Ran python3 [local path] at 2026-10-15T19:07:16–19:07:17Z. It launched this checkout's server.py and CLI with the supplied environment.json, preserving PATH and confining sockets/state to the private runtime. Baseline CLI rejected '--idempotency-key baseline-key' with exit 2. Real Unix-socket RPC confirmed backend prerequisites. Commands, timestamps, responses and readiness are saved in diagnostics/triage-witness.json; bounded daemon logs accompany it. Daemon PIDs 53614 and 53663 both exited 0 and were reaped.

Recorded command/output on the pinned source:

/bin/zsh -lc "git status --short --untracked-files=all
git diff --exit-code
git rev-parse HEAD
python3 - <<'PY'
from pathlib import Path
import json
p = Path('tests/runtime-cli-task-idempotency.test.py')
root = Path('[local path]')
events = json.loads((root / 'triage-witness.json').read_text())
started = [e['pid'] for e in events if e['kind'] == 'daemon-start']
reaped = [e['pid'] for e in events if e['kind'] == 'daemon-reaped']
assert started == reaped
print(json.dumps({'planned_test_exists':p.exists(), 'daemon_pids_started':started, 'daemon_pids_reaped':reaped, 'diagnostics':str(root)}))
PY"
f7cb113121e840bd1564232f00b2c8d8e42ee5a5
{"planned_test_exists": false, "daemon_pids_started": [53614, 53663], "daemon_pids_reaped": [53614, 53663], "diagnostics": "[local path]"}

Exit: 0

After

  • acceptance-1: The real-socket CLI integration test confirmed separate invocations with identical key, text and priority return the same taskId with exactly one queued task and unchanged persisted files. Transport capture confirmed verbatim key forwarding, including Unicode and surrounding whitespace.

  • acceptance-2: A transport-only relay discarded the successful daemon response after publication. Explicit CLI retries recovered the captured taskId with one unchanged publication, both without restart and after replacing daemon PID 92301 with PID 92303. Both lost-response-recovered receipts are preserved in verify-cli-task-idempotency.json.

  • acceptance-3: Completed and archived submission tests returned the original taskId with state done after daemon restart. Neither recreated a queue entry or changed persisted task/result files. Lifecycle states were synthetic filesystem fixtures; no task consumer was run.

  • acceptance-4: Two identical unkeyed submissions and two submissions with different keys produced four distinct taskIds and four task files. Transport capture confirmed unkeyed CLI requests omit idempotencyKey.

  • acceptance-5: Reusing a key with changed text or priority returned backend conflict errors without changing the original task or receipt. Empty, whitespace-only, 1025-byte ASCII and 1026-byte Unicode keys failed without publication. A 1024-byte Unicode key succeeded.

  • acceptance-6: Help assertions verified the option, explicit retry semantics, completion/restart behavior and absence of automatic retries. Tests inherited the supplied scratch environment and PATH, used isolated state and real Unix sockets beneath the private runtime, and bounded execution. All 13 daemon processes exited successfully, were reaped and were confirmed absent; relay threads joined and temporary fixtures were removed. Startup logs, readiness responses and cleanup evidence are retained in verify-cli-task-idempotency.json and verify-identity.json.

  • check-cli-task-idempotency: python3 tests/runtime-cli-task-idempotency.test.py ran on the pinned candidate from 2026-10-15T19:17:04.121652Z to 19:17:09.461976Z with a 90-second timeout; exit 0. Output: 'Ran 10 tests in 5.119s', 'OK'. Full output: [local path]

  • check-existing-task-contract: python3 tests/runtime-api-tasks-view.test.py ran on the pinned candidate from 2026-10-15T19:17:04.121749Z to 19:17:05.675355Z with a 90-second timeout; exit 0. Output: 'Ran 73 tests in 1.325s', 'OK'. Full output: [local path]

Test plan

python3 tests/runtime-cli-task-idempotency.test.py

Observed: PASS; exit 0.

test_archived_retry_does_not_requeue (__main__.TaskIdempotencyTests.test_archived_retry_does_not_requeue) ... {"event": "daemon-ready", "at": 1792091755.657472, "test": "__main__.TaskIdempotencyTests.test_archived_retry_does_not_requeue", "pid": 87845, "argv": ["/opt/homebrew/opt/python@3.13/bin/python3.13", "[local path]"], "socket": "[local path]", "response": {"jsonrpc": "2.0", "id": "ready", "result": {"tasks": []}}}
{"event": "daemon-reaped", "at": 1792091755.76515, "test": "__main__.TaskIdempotencyTests.test_archived_retry_does_not_requeue", "pid": 87845, "returncode": 0, "log": "[runtime-api] listening on [local path] (actor=cli-idempotency-test)\n"}
{"event": "daemon-ready", "at": 1792091755.936358, "test": "__main__.TaskIdempotencyTests.test_archived_retry_does_not_requeue", "pid": 87848, "argv": ["/opt/homebrew/opt/python@3.13/bin/python3.13", "[local path]"], "socket": "[local path]", "response": {"jsonrpc": "2.0", "id": "ready", "result": {"tasks": []}}}
{"event": "daemon-reaped", "at": 1792091756.116877, "test": "__main__.TaskIdempotencyTests.test_archived_retry_does_not_requeue", "pid": 87848, "returncode": 0, "log": "[runtime-api] listening on [local path] (actor=cli-idempotency-test)\n"}
ok
test_completed_retry_does_not_requeue (__main__.TaskIdempotencyTests.test_completed_retry_does_not_requeue) ... {"event": "daemon-ready", "at": 1792091756.24987, "test": "__main__.TaskIdempotencyTests.test_completed_retry_does_not_requeue", "pid": 87851, "argv": ["/opt/homebrew/opt/python@3.13/bin/python3.13", "[local path]"], "socket": "[local path]", "response": {"jsonrpc": "2.0", "id": "ready", "result": {"tasks": []}}}
{"event": "daemon-reaped", "at": 1792091756.345427, "test": "__main__.TaskIdempotencyTests.test_completed_retry_does_not_requeue", "pid": 87851, "returncode": 0, "log": "[runtime-api] listening on [local path] (actor=cli-idempotency-test)\n
[Excerpt: middle omitted; full recorded output SHA256 1c9963f816e19d4d0dd67a8b31d662a65d759a1b508a79d2d819107a4ca5052d]
rded": {"taskId": "task-rtapi-81836d0742f64b031790879ebc62b4c3be2cde6c7ee0046fa766cb044ea3d451", "state": "pending"}, "recovered": {"taskId": "task-rtapi-81836d0742f64b031790879ebc62b4c3be2cde6c7ee0046fa766cb044ea3d451", "state": "pending"}, "publications": 1}
{"event": "daemon-reaped", "at": 1792091758.395526, "test": "__main__.TaskIdempotencyTests.test_lost_response_retry_after_daemon_restart", "pid": 87990, "returncode": 0, "log": "[runtime-api] listening on [local path] (actor=cli-idempotency-test)\n"}
ok
test_lost_response_retry_same_daemon (__main__.TaskIdempotencyTests.test_lost_response_retry_same_daemon) ... {"event": "daemon-ready", "at": 1792091758.600779, "test": "__main__.TaskIdempotencyTests.test_lost_response_retry_same_daemon", "pid": 88017, "argv": ["/opt/homebrew/opt/python@3.13/bin/python3.13", "[local path]"], "socket": "[local path]", "response": {"jsonrpc": "2.0", "id": "ready", "result": {"tasks": []}}}
{"event": "lost-response-recovered", "at": 1792091758.787145, "restart": false, "discarded": {"taskId": "task-rtapi-81836d0742f64b031790879ebc62b4c3be2cde6c7ee0046fa766cb044ea3d451", "state": "pending"}, "recovered": {"taskId": "task-rtapi-81836d0742f64b031790879ebc62b4c3be2cde6c7ee0046fa766cb044ea3d451", "state": "pending"}, "publications": 1}
{"event": "daemon-reaped", "at": 1792091758.802926, "test": "__main__.TaskIdempotencyTests.test_lost_response_retry_same_daemon", "pid": 88017, "returncode": 0, "log": "[runtime-api] listening on [local path] (actor=cli-idempotency-test)\n"}
ok
test_maximum_utf8_key_is_accepted (__main__.TaskIdempotencyTests.test_maximum_utf8_key_is_accepted) ... {"event": "daemon-ready", "at": 1792091758.9850292, "test": "__main__.TaskIdempotencyTests.test_maximum_utf8_key_is_accepted", "pid": 88020, "argv": ["/opt/homebrew/opt/python@3.13/bin/python3.13", "[local path]"], "socket": "[local path]", "response": {"jsonrpc": "2.0", "id": "ready", "result": {"tasks": []}}}
{"event": "daemon-reaped", "at": 1792091759.1796532, "test": "__main__.TaskIdempotencyTests.test_maximum_utf8_key_is_accepted", "pid": 88020, "returncode": 0, "log": "[runtime-api] listening on [local path] (actor=cli-idempotency-test)\n"}
ok
test_same_key_separate_invocations_and_verbatim_forwarding (__main__.TaskIdempotencyTests.test_same_key_separate_invocations_and_verbatim_forwarding) ... {"event": "daemon-ready", "at": 1792091759.301674, "test": "__main__.TaskIdempotencyTests.test_same_key_separate_invocations_and_verbatim_forwarding", "pid": 88023, "argv": ["/opt/homebrew/opt/python@3.13/bin/python3.13", "[local path]"], "socket": "[local path]", "response": {"jsonrpc": "2.0", "id": "ready", "result": {"tasks": []}}}
{"event": "daemon-reaped", "at": 1792091759.610425, "test": "__main__.TaskIdempotencyTests.test_same_key_separate_invocations_and_verbatim_forwarding", "pid": 88023, "returncode": 0, "log": "[runtime-api] listening on [local path] (actor=cli-idempotency-test)\n"}
ok
test_unkeyed_and_different_keys_create_distinct_tasks (__main__.TaskIdempotencyTests.test_unkeyed_and_different_keys_create_distinct_tasks) ... {"event": "daemon-ready", "at": 1792091759.7991538, "test": "__main__.TaskIdempotencyTests.test_unkeyed_and_different_keys_create_distinct_tasks", "pid": 88039, "argv": ["/opt/homebrew/opt/python@3.13/bin/python3.13", "[local path]"], "socket": "[local path]", "response": {"jsonrpc": "2.0", "id": "ready", "result": {"tasks": []}}}
{"event": "daemon-reaped", "at": 1792091760.2160919, "test": "__main__.TaskIdempotencyTests.test_unkeyed_and_different_keys_create_distinct_tasks", "pid": 88039, "returncode": 0, "log": "[runtime-api] listening on [local path] (actor=cli-idempotency-test)\n"}
ok

----------------------------------------------------------------------
Ran 10 tests in 4.719s

OK
python3 tests/runtime-api-tasks-view.test.py

Observed: PASS; exit 0.

test_archived_after_the_ready_read_still_returns_the_answer (__main__.ArchivalRaceTests.test_archived_after_the_ready_read_still_returns_the_answer) ... ok
test_archived_during_enumeration_is_absent_not_an_error (__main__.ArchivalRaceTests.test_archived_during_enumeration_is_absent_not_an_error) ... ok
test_positive_control_the_result_is_listed_normally (__main__.ArchivalRaceTests.test_positive_control_the_result_is_listed_normally) ... ok
test_list_process_missing_acknowledgment_fails (__main__.DispatchTests.test_list_process_missing_acknowledgment_fails) ... ok
test_list_separate_process_claim_archive_and_control (__main__.DispatchTests.test_list_separate_process_claim_archive_and_control) ... ok
test_submit_idempotency_rejects_invalid_keys_and_collisions (__main__.DispatchTests.test_submit_idempotency_rejects_invalid_keys_and_collisions) ... ok
test_submit_idempotency_retry_after_restart_and_completion (__main__.DispatchTests.test_submit_idempotency_retry_after_restart_and_completion) ... ok
test_submit_status_result_dispatch (__main__.DispatchTests.test_submit_status_result_dispatch) ... ok
test_submit_without_key_remains_non_idempotent (__main__.DispatchTests.test_submit_without_key_remains_non_idempotent) ... ok
test_unconfigured_tasks_fails_loudly (__main__.DispatchTests.test_unconfigured_tasks_fails_loudly) ... ok
test_empty_result_is_not_a_terminal_state (__main__.ForeignTaskOwnershipTests.test_empty_result_is_not_a_terminal_state) ... ok
test_foreign_result_is_not_readable_by_id (__main__.ForeignTaskOwnershipTests.test_foreign_result_is_not_readable_by_id) ... ok
test_foreign_task_cannot_be_cancelled (__main__.ForeignTaskOwnershipTests.test_foreign_task_cannot_be_cancelled) ... ok
test_foreign_task_details_are_not_exposed (__main__.ForeignTaskOwnershipTests.test_foreign_task_details_are_not_exposed) ... ok
test_foreign_task_is_not_enumerated (__main__
[Excerpt: middle omitted; full recorded output SHA256 4e7a4e1fc3b1e43b95e85d5d23ac57b5557a1fecd03837260b58963124480786]
(__main__.TasksViewTests.test_cancel_done_task_is_noop_and_unknown_raises) ... ok
test_cancel_writes_cancel_instruction_signal (__main__.TasksViewTests.test_cancel_writes_cancel_instruction_signal) ... ok
test_details_roundtrip (__main__.TasksViewTests.test_details_roundtrip) ... ok
test_genuine_gateway_suffix_still_resolves_beside_the_longer_id (__main__.TasksViewTests.test_genuine_gateway_suffix_still_resolves_beside_the_longer_id) ... ok
test_get_result_live_and_archived (__main__.TasksViewTests.test_get_result_live_and_archived) ... ok
test_get_result_monthly_and_gateway_archives (__main__.TasksViewTests.test_get_result_monthly_and_gateway_archives) ... ok
test_get_result_no_id_empty_is_none (__main__.TasksViewTests.test_get_result_no_id_empty_is_none) ... ok
test_get_result_no_id_returns_newest_runtime_api_only (__main__.TasksViewTests.test_get_result_no_id_returns_newest_runtime_api_only) ... ok
test_list_results_runtime_api_only_newest_first (__main__.TasksViewTests.test_list_results_runtime_api_only_newest_first) ... ok
test_list_results_truncated_counts_ready_not_files (__main__.TasksViewTests.test_list_results_truncated_counts_ready_not_files) ... ok
test_list_results_window_is_spent_on_ready_answers_only (__main__.TasksViewTests.test_list_results_window_is_spent_on_ready_answers_only) ... ok
test_list_tasks_disappearing_during_metadata_access (__main__.TasksViewTests.test_list_tasks_disappearing_during_metadata_access) ... ok
test_list_tasks_empty_dir (__main__.TasksViewTests.test_list_tasks_empty_dir) ... ok
test_list_tasks_enumerates_live_only (__main__.TasksViewTests.test_list_tasks_enumerates_live_only) ... ok
test_list_tasks_retains_mtime_and_limit_semantics (__main__.TasksViewTests.test_list_tasks_retains_mtime_and_limit_semantics) ... ok
test_longer_task_ids_gateway_archive_is_not_this_task (__main__.TasksViewTests.test_longer_task_ids_gateway_archive_is_not_this_task)
A longer id's epoch-suffixed archive shares this id's prefix. ... ok
test_metadata_scans_propagate_operational_errors (__main__.TasksViewTests.test_metadata_scans_propagate_operational_errors) ... ok
test_queue_priority_roundtrip_order_and_body_confinement (__main__.TasksViewTests.test_queue_priority_roundtrip_order_and_body_confinement) ... ok
test_result_disappearing_during_metadata_access (__main__.TasksViewTests.test_result_disappearing_during_metadata_access) ... ok
test_status_broken_hitl_lookup_fails_open (__main__.TasksViewTests.test_status_broken_hitl_lookup_fails_open) ... ok
test_status_lifecycle_pending_claimed_done (__main__.TasksViewTests.test_status_lifecycle_pending_claimed_done) ... ok
test_status_unknown_task (__main__.TasksViewTests.test_status_unknown_task) ... ok
test_status_waiting_for_states_from_hitl_lookup (__main__.TasksViewTests.test_status_waiting_for_states_from_hitl_lookup) ... ok
test_submit_confines_newline_injection (__main__.TasksViewTests.test_submit_confines_newline_injection) ... ok
test_submit_rejects_empty_and_bad_priority (__main__.TasksViewTests.test_submit_rejects_empty_and_bad_priority) ... ok
test_submit_stamps_instance_id_when_scoped (__main__.TasksViewTests.test_submit_stamps_instance_id_when_scoped) ... ok
test_submit_writes_canonical_headers_real_parser_roundtrip (__main__.TasksViewTests.test_submit_writes_canonical_headers_real_parser_roundtrip) ... ok
test_get_result_traversal_reads_as_absent (__main__.TraversalGuardTests.test_get_result_traversal_reads_as_absent) ... ok
test_hostile_ids_absent_on_every_entry_point (__main__.TraversalGuardTests.test_hostile_ids_absent_on_every_entry_point) ... ok
test_legit_ids_still_resolve (__main__.TraversalGuardTests.test_legit_ids_still_resolve) ... ok

----------------------------------------------------------------------
Ran 73 tests in 1.323s

OK

Canonical review scanner:

review-checks: PASS (hardcoded-paths + root-artifacts + prose-cap clean)
prose-cap: PASS (comment blocks within cap 2, exts .py)

Verified candidate: 674ecea6d90e3bb8778858413b9b2e77f754c33e. Publication commit: dacc048154311f5324837092cd60e1e72b41d777. Both have tree 90c5105ace3339c2c7b4342bc2a90aac50016da1; only factory-authored commit metadata changed. Acceptance evidence refers to the verified candidate; the scanner ran on the publication tree.

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at dacc048. Verified myself:

  • The server-side idempotency machinery (src/runtime-api/dispatcher.py, request_store.py, tasks_view.py.submit_idempotent) already exists and already handles key storage, fingerprint mismatch on key reuse with different task text, and recovery after a lost response or daemon restart. protocol.py already documents task.submit {task, priority?, idempotencyKey?}. This PR is exactly what it says: a thin CLI flag (--idempotency-key) that forwards idempotencyKey into the existing RPC call — no new server logic, 15 lines in sutando-runtime.py.
  • Ran tests/runtime-cli-task-idempotency.test.py myself: 10/10 pass, including real spawned-daemon scenarios (test_lost_response_retry_after_daemon_restart, test_lost_response_retry_same_daemon) — not just mocks.
  • scripts/review-checks.sh on the diff: clean.
  • Server-side validation already rejects an empty/whitespace key (tasks_view.py:105), so the CLI doesn't need to duplicate that check.

No blockers. Approving.

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Gate

✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 91%.

Diff Coverage

Diff: origin/main...HEAD, staged and unstaged changes

  • src/runtime-cli/sutando-runtime.py (100%)

Summary

  • Total: 5 lines
  • Missing: 0 lines
  • Coverage: 100%

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at head dacc048154311f5324837092cd60e1e72b41d777 (one commit on f7cb1131; diff is src/runtime-cli/sutando-runtime.py +11/−4 and a new tests/runtime-cli-task-idempotency.test.py). Fresh worktree.

What the diff does, checked against the tree. The CLI gains --idempotency-key on task submit and forwards it as idempotencyKey only when given (sutando-runtime.py:1009-1012); an unkeyed submit sends exactly the old params. Nothing else changes. The policy is where the architecture rules put it: server.py is transport (its own comment at :139), dispatcher.py:303-306 routes a keyed submit to TasksView.submit_idempotent, and the receipt is a hard link under tasks/.runtime-api-idempotency/<sha256(key)>.task with the task id derived from the key (tasks_view.py:97-130). That backend predates this PR (#2542 lineage) and keeps its own tests; the PR adds no dedupe machinery of its own.

My own probe, real server.py + real CLI over a Unix socket in an isolated workspace (same fixture env as the PR test):

same key x2            → same taskId, state pending both times, queue 1
different key          → second taskId, queue 2
same key, other text   → exit 1  {"error": "-32602: idempotency key is already bound to a different task submission"}, queue unchanged
same key, other prio   → same refusal, queue unchanged
unkeyed x2             → two taskIds, queue 4
daemon terminated and restarted, same key → original taskId, state pending, queue unchanged
task moved to tasks/archive, same key → original taskId, state done, nothing requeued (queue 3)

So the key dedupes, a different body under the same key is refused rather than replayed, and the receipt survives the dispatcher/runtime boundary and a daemon restart.

Suites at head: runtime-cli-task-idempotency 10/10, runtime-api-tasks-view 73/73, and every other runtime-api-* / runtime-cli-* suite (26 files) green; scripts/review-checks.sh --diff PASS.

REVIEW.md §15. This is a live path (CLI → Unix socket → runtime-api submit). The body's witness is a real one: the daemon run as a subprocess, a transport-only relay dropping the successful response, then the identical command recovering the original id both on the same daemon and after replacing the daemon PID (test_lost_response_retry_after_daemon_restart is that scenario, and the body's monitor run repeats it by hand). I reproduced the restart case above. It is on an isolated candidate, not the installed service — for a flag that only forwards a parameter to a backend that already shipped with its own round trip, that is sufficient; noting it so the owner knows what was and was not exercised.

Nits, non-blocking: the body's checklist boxes are all unticked although the text below them answers each; and --idempotency-key help could say the key is scoped to one workspace's tasks/ (the receipt lives there), since an operator retrying against a different host gets a new task.

APPROVE at dacc0481. Merge is the owner's.

@sonichi

sonichi commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to my approval: the "diff coverage >= 95% (python)" gate is now red — missed line 1011 in src/runtime-cli/sutando-runtime.py (params["idempotencyKey"] = args.idempotency_key), 80% vs the 95% floor.

Root cause, confirmed by reading the actual CI step: tests/runtime-cli-task-idempotency.test.py spawns both the daemon (start_daemon, line 85: command = [sys.executable, str(SERVER)]) and the CLI (cli(), line 139-140: [sys.executable, str(CLI), *args]) as plain subprocesses — neither goes through the repo's established SUTANDO_TEST_SUBPROCESS_COVERAGE wrapper, so coverage.py never instruments the child process and can't see lines only exercised inside it. That's exactly line 1011 — the test does genuinely exercise it (my own full run passed 10/10, including the idempotency-key cases), the gate just can't see it.

Fix, same pattern already used by tests/runtime-cli-surface.test.py:28-30 (and 14 other test files in this repo):

PYBASE = [sys.executable]
if os.environ.get("SUTANDO_TEST_SUBPROCESS_COVERAGE") == "1":
    PYBASE += ["-m", "coverage", "run", f"--rcfile={ROOT / '.coveragerc'}"]

then use PYBASE + [str(SERVER)] at line 85 and PYBASE + [str(CLI), *args] at line 139-140 instead of the bare sys.executable forms.

Not touching your PR myself — flagging per your own instruction ("if blocked, reply here with the issue"). Review: #5595 (review) (or see the PR's review list for mine, approved before this gate result came in — approval still stands, this is a CI mechanics gap, not a code concern).

@github-actions

Copy link
Copy Markdown
Contributor

@cla-assistant check

@keweichen

Copy link
Copy Markdown
Collaborator Author

Addressed review feedback in bfd7cf71896a2d5b9cc17eb3da6f0a64d631a971.

  • acceptance-1: Separate real CLI invocations with key ' verbatim-é-42 ', identical text and urgent priority returned the same taskId. A transparent Unix-socket relay observed the supplied key forwarded verbatim as idempotencyKey. Exactly one task was published; content, inode and modification time remained unchanged. The real watcher emitted one notification.

  • acceptance-2: A transport-only relay discarded the real daemon's successful response after publication. The CLI failed visibly; explicit retries before and after daemon restart recovered task-rtapi-8725917f952616a2a316eb83c2baa4f1a47c34e20bf6944f72fe2e639f620264 without changing persisted task files or producing another watcher notification. Command: python3 [local path] Daemon PID 94435 started at 2026-10-16T03:04:12.130Z, was reaped at 03:04:19.916Z, and replacement PID 95185 started at 03:04:19.918Z. Full commands, readiness responses and outcomes are in diagnostics/runtime-witness.jsonl.

  • acceptance-3: Using isolated completed-result and archived-task lifecycle fixtures, retries after another daemon restart returned each original taskId with state=done. No task was requeued; persisted files remained unchanged. The real CLI also retrieved the completed result. These fixtures prove retry behavior for existing lifecycle states; no model execution is claimed.

  • acceptance-4: Two identical unkeyed submissions and two submissions with different keys produced four distinct taskIds and four task files. Captured unkeyed RPC parameters contained only task and priority, with idempotencyKey omitted.

  • acceptance-5: Reusing a key with changed text or priority returned backend error -32602 without altering the original submission. Empty, whitespace-only, 1025-byte ASCII and 1026-byte UTF-8 keys were rejected without publication. The focused suite also accepted and deduplicated a 1024-byte UTF-8 key.

  • acceptance-6: Real CLI help documents explicit retry semantics, completion/restart recovery, unkeyed behavior and no automatic retries. All product subprocesses inherited the supplied scratch environment; sockets and state stayed private. The watcher ran through bash src/watch-tasks-stream.sh --role session --inbox [local path] using real recursive fswatch poll_monitor, after sentinel readiness and the standby wait. Parent wait receipts cover all 30 daemon/watcher starts across the test and witness runs; the in-session cleanup query found no remaining fswatch, and the sentinel was removed. Additional retrospective process queries were unavailable after their command sessions ended; cleanup evidence comes from the parent wait receipts.

  • feedback-1: Ran the current integration suite with SUTANDO_TEST_SUBPROCESS_COVERAGE=1 using coverage 7.16.2 and the unchanged .coveragerc; all 10 tests passed under instrumentation. Combined subprocess data and generated coverage XML, then executed BASE_REF=f7cb113121e840bd1564232f00b2c8d8e42ee5a5 COVERAGE_GATE_PRECOMPUTED=1 bash scripts/coverage-gate.sh with the default 95% threshold. This base is the parent of the original feature commit, so the full feature diff was measured. Actual output: src/runtime-cli/sutando-runtime.py (100%); Total: 5 lines; Missing: 0 lines; Coverage: 100%; exit 0. Gate configuration and exclusions were unchanged. Log: diagnostics/coverage/product-gate.log.

Running-product observations:

  • monitor-user-journey: Ran python3 [local path] against the exact candidate. It started python3 src/runtime-api/server.py as PID 54954 at 2026-10-16T03:16:31.955Z; task.list confirmed readiness at 03:16:32.113Z. Started bash src/watch-tasks-stream.sh --role session --inbox [local path] as PID 54961, waited for its resolved sentinel and standby interval, and used the provisioned real recursive fswatch poll_monitor backend. Command: python3 src/runtime-cli/sutando-runtime.py task submit 'Summarize synthetic release planning notes' --priority urgent --idempotency-key ' monitor-résumé-42 '. A transport-only Unix relay discarded the successful daemon response; the CLI exited 1 with an error. The daemon had published task-rtapi-37a4a7ce56a658b366a80b43c891312690f8576a681c1770ca80670b96306f36, and the watcher emitted its TASK_FILE notification. Commands, responses and persisted snapshots: diagnostics/monitor-journey.jsonl under the private runtime directory.

  • monitor-repeatability: Repeated the same CLI submission once before restart and twice afterward. PID 54954 exited 0 at 03:16:37.221Z; replacement PID 55524 started at 03:16:37.224Z and became ready at 03:16:37.501Z. Every retry exited 0 and returned the original taskId with state=pending. The recorded retry-proof output reports retries=3, publications=[original taskId], persisted_unchanged=true and exactly one TASK_FILE notification. Task contents, inode and modification time remained identical.

  • acceptance-1: Separate CLI invocations with identical key, text and urgent priority returned the same taskId and left exactly one task published. The relay captured idempotencyKey=' monitor-résumé-42 ' verbatim, including spaces and Unicode. File snapshots and watcher output confirmed no republication.

  • acceptance-2: The relay captured an actual successful daemon response and published task before closing without delivering that response. Explicit CLI retries recovered the original taskId both before and after restarting the disposable daemon. The task snapshot remained unchanged and its watcher notification count remained one.

  • acceptance-3: Created isolated completed-result and archived-task lifecycle fixtures, restarting the real daemon after each transition. CLI retries returned each original taskId with state=done, with no queued tasks or lifecycle-file changes. CLI task get-result returned 'Synthetic summary complete: action items recorded.' These fixtures establish persisted-state retry behavior; they do not claim execution by a model.

  • acceptance-4: Two identical unkeyed submissions returned distinct IDs task-rtapi-4718afa6346e and task-rtapi-45082ffdcedf. Submissions keyed monitor-distinct-a and monitor-distinct-b added two further distinct IDs. CLI task list showed exactly four pending tasks, and the watcher observed each. Captured unkeyed RPC parameters contained only task and priority.

  • acceptance-5: Changing text or priority while retaining the key returned exit 1 and backend error -32602: idempotency key is already bound to a different task submission. Empty, whitespace-only, 1025-byte ASCII and 1026-byte UTF-8 keys returned the existing required/too-long validation errors. All six rejections preserved the original snapshot and publication count. The focused suite also passed its accepted 1024-byte UTF-8 boundary case.

  • acceptance-6: python3 src/runtime-cli/sutando-runtime.py task submit --help exited 0 and documented same-key/text/priority explicit retries, original taskId recovery, completion and daemon restart, unkeyed behavior and no automatic retries. Product subprocesses loaded the supplied scratch environment; sockets and temporary state stayed beneath [local path] Test runs had 90-second outer limits, bounded CLI/readiness waits and cleanup. Both ordinary and instrumented integration runs recorded 13 daemon starts and 13 successful reap receipts each. The initial observer used an incorrect sentinel filename; resolving it through src/util_paths.py corrected observation without product edits.

  • feedback-1: Ran both frozen suites under python3 -m coverage run --rcfile=.coveragerc with SUTANDO_TEST_SUBPROCESS_COVERAGE=1; both passed. Combined subprocess data and generated XML. Then ran BASE_REF=f7cb113121e840bd1564232f00b2c8d8e42ee5a5 COVERAGE_GATE_PRECOMPUTED=1 bash scripts/coverage-gate.sh against the original feature base, retaining the unchanged 95% threshold. Actual output: src/runtime-cli/sutando-runtime.py (100%); Total: 5 lines; Missing: 0 lines; Coverage: 100%; exit 0. XML independently recorded hits for lines 878, 1009, 1010, 1011 and 1012. Git confirmed .coveragerc and coverage-gate.sh were unchanged. Evidence: diagnostics/coverage/product-gate.log and changed-line-hits.json. This establishes current-head local coverage, not a remote CI rerun.

/bin/zsh -c "python3 - <<'PY'
import os,json,pathlib,subprocess,shutil,datetime,time,xml.etree.ElementTree as ET
R=pathlib.Path.cwd(); B=pathlib.Path('[local path]'); D=B/'diagnostics'; C=D/'coverage'; E=os.environ.copy(); E.update(json.loads((B/'environment.json').read_text())); E.update(PYTHONDONTWRITEBYTECODE='1',TMPDIR=str(B/'tmp'),SUTANDO_TELEMETRY='0',COVERAGE_GATE_PRECOMPUTED='1',BASE_REF='f7cb113121e840bd1564232f00b2c8d8e42ee5a5')
E.pop('COVERAGE_GATE_FAIL_UNDER',None); E.pop('COVERAGE_FILE',None)
created=['.coverage','coverage.xml','diff-cover.md','coverage-summary.md']
assert all(not (R/name).exists() for name in created),'would replace existing coverage artifact'
try:
 shutil.copy2(C/'.coverage',R/'.coverage'); shutil.copy2(C/'coverage.xml',R/'coverage.xml')
 started=datetime.datetime.now(datetime.timezone.utc).isoformat(); begin=time.monotonic(); argv=['bash','scripts/coverage-gate.sh']
 p=subprocess.run(argv,cwd=R,env=E,text=True,capture_output=True,timeout=90)
 (C/'product-gate.log').write_text(p.stdout+p.stderr)
 print(json.dumps({'argv':argv,'env':{'BASE_REF':E['BASE_REF'],'COVERAGE_GATE_PRECOMPUTED':'1'},'at':started,'exit':p.returncode,'seconds':round(time.monotonic()-begin,3)})); print(p.stdout); print(p.stderr)
 (C/'product-gate-receipt.json').write_text(json.dumps({'argv':argv,'base':E['BASE_REF'],'at':started,'exit':p.returncode}))
 for name in ('diff-cover.md','coverage-summary.md'):
  if (R/name).exists(): shutil.copy2(R/name,C/name)
 assert p.returncode==0
 assert 'Total:   5 lines' in p.stdout and 'Missing: 0 lines' in p.stdout and 'Coverage: 100%' in p.stdout
 xml=ET.parse(C/'coverage.xml'); observed={}
 for cls in xml.findall('.//class'):
  if cls.get('filename')=='src/runtime-cli/sutando-runtime.py':
   observed={int(line.get('number')):int(line.get('hits')) for line in cls.findall('lines/line') if int(line.get('number')) in (878,1009,1010,1011,1012)}
 print('changed-line-hits',observed); assert len(observed)==5 and all(observed.values())
 (C/'changed-line-hits.json').write_text(json.dumps(observed))
finally:
 for name in created: (R/name).unlink(missing_ok=True)
for argv in (['git','rev-parse','HEAD'],['git','status','--porcelain'],['git','diff','--exit-code'],['git','diff','--cached','--exit-code']):
 p=subprocess.run(argv,cwd=R,env=E,text=True,capture_output=True,timeout=10); print(json.dumps({'argv':argv,'exit':p.returncode,'stdout':p.stdout,'stderr':p.stderr})); assert p.returncode==0
PY"
{"argv": ["bash", "scripts/coverage-gate.sh"], "env": {"BASE_REF": "f7cb113121e840bd1564232f00b2c8d8e42ee5a5", "COVERAGE_GATE_PRECOMPUTED": "1"}, "at": "2026-10-16T03:17:08.619405+00:00", "exit": 0, "seconds": 8.135}
coverage-gate: using precomputed coverage data (.coverage, coverage.xml).

── whole-tree coverage (informational) ─────────────────────────
src/hitl/schema.py                                                      124     57    54%
src/runtime-api/rundir.py                                                72     32    56%
src/task_envelope.py                                                     90     40    56%
src/runtime-api/server.py                                               313    126    60%
src/task_priority.py                                                     48     17    65%
src/file_lock.py                                                         48     15    69%
src/runtime-api/instance_key.py                                          48     13    73%
src/runtime-api/protocol.py                                              33      7    79%
src/runtime-api/schedules_view.py                                        11      1    91%
src/delivery/readiness.py                                                37      3    92%
src/runtime-api/tasks_view.py                                           260      7    97%
-----------------------------------------------------------------------------------------
TOTAL                                                                 48991  47066     4%

10 files skipped due to complete coverage.
TOTAL: 4%
─────────────────────────────────────────────────────────────────

coverage-gate: enforcing 95% on lines changed vs f7cb113121e840bd1564232f00b2c8d8e42ee5a5
-------------
Diff Coverage
Diff: f7cb113121e840bd1564232f00b2c8d8e42ee5a5...HEAD, staged and unstaged changes
-------------
src/runtime-cli/sutando-runtime.py (100%)
-------------
Total:   5 lines
Missing: 0 lines
Coverage: 100%
-------------


[local path] UserWarning: The --markdown-report option is deprecated. Use --format markdown:diff-cover.md instead.
  warnings.warn(

changed-line-hits {}
Traceback (most recent call last):
  File "<stdin>", line 21, in <module>
AssertionError

Exit: 1

/bin/zsh -c "python3 - <<'PY'
from pathlib import Path
import json,xml.etree.ElementTree as ET,subprocess,hashlib,datetime
b=Path('[local path]'); d=b/'diagnostics'; c=d/'coverage'; root=ET.parse(c/'coverage.xml'); rows=[]
for cls in root.findall('.//class'):
 if cls.get('filename','').endswith('runtime-cli/sutando-runtime.py'):
  hits={int(x.get('number')):int(x.get('hits')) for x in cls.findall('lines/line') if int(x.get('number')) in (878,1009,1010,1011,1012)}; rows.append({'xml_filename':cls.get('filename'),'hits':hits}); assert len(hits)==5 and all(hits.values())
assert len(rows)==1; print(json.dumps(rows)); (c/'changed-line-hits.json').write_text(json.dumps(rows,indent=2))
records=[]
for argv in (['git','rev-parse','HEAD'],['git','status','--porcelain'],['git','diff','--exit-code'],['git','diff','--cached','--exit-code'],['git','diff','dacc048154311f5324837092cd60e1e72b41d777','HEAD','--name-only'],['git','diff','f7cb113121e840bd1564232f00b2c8d8e42ee5a5','HEAD','--','.coveragerc','scripts/coverage-gate.sh']):
 p=subprocess.run(argv,text=True,capture_output=True,timeout=10); r={'argv':argv,'exit':p.returncode,'stdout':p.stdout,'stderr':p.stderr}; records.append(r); print(json.dumps(r)); assert p.returncode==0
 if argv[1] in ('status',) or argv[-1]=='scripts/coverage-gate.sh': assert not p.stdout
(d/'final-checkout.json').write_text(json.dumps(records,indent=2))
for name in ['cli-task-idempotency','instrumented-cli-task-idempotency']:
 events=[]
 for line in (d/(name+'.log')).read_text().splitlines():
  if line.startswith('{'):
   try: events.append(json.loads(line))
   except ValueError: pass
 started={e['pid'] for e in events if e.get('event')=='daemon-ready'}; reaped={e['pid'] for e in events if e.get('event')=='daemon-reaped' and e.get('returncode')==0}
 r={'log':name,'daemon_ready':len(started),'daemon_reaped':len(reaped),'all_reaped':started==reaped}; print(json.dumps(r)); assert started==reaped and len(started)==13
(d/'artifact-hashes.json').write_text(json.dumps({str(p.relative_to(d)):hashlib.sha256(p.read_bytes()).hexdigest() for p in d.rglob('*') if p.is_file() and p.name"'!='"'artifact-hashes.json'},indent=2))
PY"
[{"xml_filename": "runtime-cli/sutando-runtime.py", "hits": {"878": 1, "1009": 1, "1010": 1, "1011": 1, "1012": 1}}]
{"argv": ["git", "rev-parse", "HEAD"], "exit": 0, "stdout": "0db3819cb29b3f282e269df82b9c2c399b764845\n", "stderr": ""}
{"argv": ["git", "status", "--porcelain"], "exit": 0, "stdout": "", "stderr": ""}
{"argv": ["git", "diff", "--exit-code"], "exit": 0, "stdout": "", "stderr": ""}
{"argv": ["git", "diff", "--cached", "--exit-code"], "exit": 0, "stdout": "", "stderr": ""}
{"argv": ["git", "diff", "dacc048154311f5324837092cd60e1e72b41d777", "HEAD", "--name-only"], "exit": 0, "stdout": "tests/runtime-cli-task-idempotency.test.py\n", "stderr": ""}
{"argv": ["git", "diff", "f7cb113121e840bd1564232f00b2c8d8e42ee5a5", "HEAD", "--", ".coveragerc", "scripts/coverage-gate.sh"], "exit": 0, "stdout": "", "stderr": ""}
{"log": "cli-task-idempotency", "daemon_ready": 13, "daemon_reaped": 13, "all_reaped": true}
{"log": "instrumented-cli-task-idempotency", "daemon_ready": 13, "daemon_reaped": 13, "all_reaped": true}

Exit: 0

Regression checks:

  • cli-task-idempotency: PASS
  • existing-task-contract: PASS
/bin/zsh -c "python3 - <<'PY'
import os,json,subprocess,time
from pathlib import Path
base=Path('[local path]'); diag=base/'diagnostics'; cov=diag/'coverage'; cov.mkdir(exist_ok=True)
env=os.environ.copy();env.update(json.loads((base/'environment.json').read_text()));env.update(TMPDIR=str(base/'tmp'),PYTHONDONTWRITEBYTECODE='1',SUTANDO_TELEMETRY='0',SUTANDO_TEST_SUBPROCESS_COVERAGE='1',COVERAGE_FILE=str(cov/'.coverage'))
commands=[['python3','-m','coverage','--version'],['diff-cover','--version'],['python3','-m','coverage','run','--rcfile=.coveragerc','tests/runtime-cli-task-idempotency.test.py'],['python3','-m','coverage','combine','--rcfile=.coveragerc'],['python3','-m','coverage','xml','--rcfile=.coveragerc','-o',str(cov/'coverage.xml')],['diff-cover',str(cov/'coverage.xml'),'--compare-branch=f7cb113121e840bd1564232f00b2c8d8e42ee5a5','--fail-under=95','--markdown-report',str(cov/'diff-cover.md')]]
receipts=[]
for i,argv in enumerate(commands):
    start=time.time();log=cov/f'command-{i+1}.log'
    with log.open('w') as out:r=subprocess.run(argv,env=env,stdout=out,stderr=subprocess.STDOUT,timeout=180)
    receipt=dict(argv=argv,start=start,end=time.time(),exit=r.returncode,log=str(log));receipts.append(receipt)
    print(json.dumps(receipt),flush=True);print(log.read_text()[-2500:],flush=True)
    (cov/'receipts.json').write_text(json.dumps(receipts,indent=2))
    if r.returncode:break
PY"
{"argv": ["python3", "-m", "coverage", "--version"], "start": 1792119720.6977248, "end": 1792119721.227648, "exit": 0, "log": "[local path]"}
Coverage.py, version 7.16.2 with C extension
Full documentation is at https://coverage.readthedocs.io/en/7.16.2

{"argv": ["diff-cover", "--version"], "start": 1792119721.228105, "end": 1792119722.9025111, "exit": 0, "log": "[local path]"}
diff-cover 10.6.0

{"argv": ["python3", "-m", "coverage", "run", "--rcfile=.coveragerc", "tests/runtime-cli-task-idempotency.test.py"], "start": 1792119722.902899, "end": 1792119734.99079, "exit": 0, "log": "[local path]"}
ns_and_verbatim_forwarding (__main__.TaskIdempotencyTests.test_same_key_separate_invocations_and_verbatim_forwarding) ... {"event": "daemon-ready", "at": 1792119732.693675, "test": "__main__.TaskIdempotencyTests.test_same_key_separate_invocations_and_verbatim_forwarding", "pid": 87073, "argv": ["/opt/homebrew/opt/python@3.13/bin/python3.13", "-m", "coverage", "run", "--rcfile=./.coveragerc", "./src/runtime-api/server.py"], "socket": "[local path]", "response": {"jsonrpc": "2.0", "id": "ready", "result": {"tasks": []}}}
{"event": "daemon-reaped", "at": 1792119733.482408, "test": "__main__.TaskIdempotencyTests.test_same_key_separate_invocations_and_verbatim_forwarding", "pid": 87073, "returncode": 0, "log": "[runtime-api] listening on [local path] (actor=cli-idempotency-test)\n"}
ok
test_unkeyed_and_different_keys_create_distinct_tasks (__main__.TaskIdempotencyTests.test_unkeyed_and_different_keys_create_distinct_tasks) ... {"event": "daemon-ready", "at": 1792119733.780071, "test": "__main__.TaskIdempotencyTests.test_unkeyed_and_different_keys_create_distinct_tasks", "pid": 87161, "argv": ["/opt/homebrew/opt/python@3.13/bin/python3.13", "-m", "coverage", "run", "--rcfile=./.coveragerc", "./src/runtime-api/server.py"], "socket": "[local path]", "response": {"jsonrpc": "2.0", "id": "ready", "result": {"tasks": []}}}
{"event": "daemon-reaped", "at": 1792119734.864779, "test": "__main__.TaskIdempotencyTests.test_unkeyed_and_different_keys_create_distinct_tasks", "pid": 87161, "returncode": 0, "log": "[runtime-api] listening on [local path] (actor=cli-idempotency-test)\n"}
ok

----------------------------------------------------------------------
Ran 10 tests in 11.780s

OK
[local path] CoverageWarning: No data was collected. (no-data-collected); see https://coverage.readthedocs.io/en/7.16.2/messages.html#warning-no-data-collected
  self._warn("No data was collected.", slug="no-data-collected")

{"argv": ["python3", "-m", "coverage", "combine", "--rcfile=.coveragerc"], "start": 1792119734.991241, "end": 1792119735.2618089, "exit": 0, "log": "[local path]"}
Combined 17 files, skipped 25

{"argv": ["python3", "-m", "coverage", "xml", "--rcfile=.coveragerc", "-o", "[local path]"], "start": 1792119735.2623, "end": 1792119738.1422122, "exit": 0, "log": "[local path]"}
Wrote XML report to [local path]

{"argv": ["diff-cover", "[local path]", "--compare-branch=f7cb113121e840bd1564232f00b2c8d8e42ee5a5", "--fail-under=95", "--markdown-report", "[local path]"], "start": 1792119738.142653, "end": 1792119739.164562, "exit": 0, "log": "[local path]"}
[local path] UserWarning: The --markdown-report option is deprecated. Use --format markdown:[local path] instead.
  warnings.warn(
-------------
Diff Coverage
Diff: f7cb113121e840bd1564232f00b2c8d8e42ee5a5...HEAD, staged and unstaged changes
-------------
src/runtime-cli/sutando-runtime.py (100%)
-------------
Total:   5 lines
Missing: 0 lines
Coverage: 100%
-------------

Exit: 0

/bin/zsh -c "python3 - <<'PY'
import json,os,shutil,subprocess,time
from pathlib import Path
root=Path.cwd();base=Path('[local path]');cov=base/'diagnostics/coverage'
env=os.environ.copy();env.update(json.loads((base/'environment.json').read_text()));env.update(TMPDIR=str(base/'tmp'),PYTHONDONTWRITEBYTECODE='1',BASE_REF='f7cb113121e840bd1564232f00b2c8d8e42ee5a5',COVERAGE_GATE_PRECOMPUTED='1',SUTANDO_TELEMETRY='0');env.pop('COVERAGE_FILE',None);env.pop('COVERAGE_GATE_FAIL_UNDER',None)
artifacts=[root/name for name in ('.coverage','coverage.xml','diff-cover.md','coverage-summary.md','coverage-gate-report.md')]
assert not any(p.exists() for p in artifacts), 'Refusing to overwrite existing artifacts'
tracked=set(subprocess.check_output(['git','ls-files'],text=True).splitlines());assert not any(p.name in tracked for p in artifacts)
try:
    for name in ('.coverage','coverage.xml'):shutil.copyfile(cov/name,root/name)
    argv=['bash','scripts/coverage-gate.sh'];start=time.time()
    with (cov/'product-gate.log').open('w') as out:r=subprocess.run(argv,env=env,stdout=out,stderr=subprocess.STDOUT,timeout=90)
    print((cov/'product-gate.log').read_text())
    receipt={'argv':argv,'BASE_REF':env['BASE_REF'],'COVERAGE_GATE_PRECOMPUTED':'1','threshold':'default 95 unchanged','data':'10 passing real-transport CLI tests, subprocess coverage enabled','start':start,'end':time.time(),'exit':r.returncode}
    print(json.dumps(receipt));(cov/'product-gate-receipt.json').write_text(json.dumps(receipt,indent=2))
    for name in ('diff-cover.md','coverage-summary.md','coverage-gate-report.md'):
        if (root/name).exists():shutil.copyfile(root/name,cov/('product-'+name))
finally:
    for p in artifacts:p.unlink(missing_ok=True)
PY"
coverage-gate: using precomputed coverage data (.coverage, coverage.xml).

── whole-tree coverage (informational) ─────────────────────────
src/runtime-api/runtime_view.py                                          35     19    46%
src/hitl/schema.py                                                      124     57    54%
src/runtime-api/rundir.py                                                72     32    56%
src/task_envelope.py                                                     90     40    56%
src/runtime-api/server.py                                               313    126    60%
src/runtime-api/tasks_view.py                                           260     83    68%
src/file_lock.py                                                         48     15    69%
src/runtime-api/instance_key.py                                          48     13    73%
src/runtime-api/protocol.py                                              33      7    79%
src/delivery/readiness.py                                                37      5    86%
src/runtime-api/schedules_view.py                                        11      1    91%
-----------------------------------------------------------------------------------------
TOTAL                                                                 48991  47352     3%

10 files skipped due to complete coverage.
TOTAL: 3%
─────────────────────────────────────────────────────────────────

coverage-gate: enforcing 95% on lines changed vs f7cb113121e840bd1564232f00b2c8d8e42ee5a5
[local path] UserWarning: The --markdown-report option is deprecated. Use --format markdown:diff-cover.md instead.
  warnings.warn(
-------------
Diff Coverage
Diff: f7cb113121e840bd1564232f00b2c8d8e42ee5a5...HEAD, staged and unstaged changes
-------------
src/runtime-cli/sutando-runtime.py (100%)
-------------
Total:   5 lines
Missing: 0 lines
Coverage: 100%
-------------


{"argv": ["bash", "scripts/coverage-gate.sh"], "BASE_REF": "f7cb113121e840bd1564232f00b2c8d8e42ee5a5", "COVERAGE_GATE_PRECOMPUTED": "1", "threshold": "default 95 unchanged", "data": "10 passing real-transport CLI tests, subprocess coverage enabled", "start": 1792119884.555744, "end": 1792119891.312876, "exit": 0}

Exit: 0

Independent verification, code review and product checks passed on candidate 0db3819cb29b3f282e269df82b9c2c399b764845; the published commit has the same tree 67cbfbf69e0b1d1283265adb955b71106e67a3d3. Please review the new head.

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at bfd7cf7. The fix is exactly the two-line SUTANDO_TEST_SUBPROCESS_COVERAGE wrapper I flagged — verified it myself rather than trusting the diff: ran the real test suite under coverage run with the wrapper active (10/10 still pass, functional behavior unchanged), then ran diff-cover against origin/main the same way CI does: 100% diff coverage on sutando-runtime.py (was 80%). scripts/review-checks.sh clean.

No remaining blockers from me. Approving at this head (my prior approval was stale, at dacc048).

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at head bfd7cf71896a2d5b9cc17eb3da6f0a64d631a971 (one commit over dacc0481). Fresh worktree.

The delta is test-only: tests/runtime-cli-task-idempotency.test.py +6/−2 — a PYBASE prefix that runs the daemon and CLI subprocesses under python -m coverage run --rcfile=.coveragerc when SUTANDO_TEST_SUBPROCESS_COVERAGE=1, otherwise plain sys.executable as before. src/ is unchanged from the head I approved (git diff dacc0481 bfd7cf71 -- src/ is empty), so every finding in that review stands.

The keyed branch is exercised and the test is load-bearing: at head the suite passes 10/10; with the two forwarding lines (sutando-runtime.py:1010-1011, if args.idempotency_key is not None: params["idempotencyKey"] = …) removed, six tests fail (test_archived_retry_does_not_requeue, test_completed_retry_does_not_requeue, four test_invalid_keys_rejected_by_backend_without_publication cases). The file is picked up by scripts/discover-python-tests.sh.

Coverage gate at this head: diff coverage >= 95% (python) is SUCCESS, gate comment "Total: 5 lines, Missing: 0 lines, Coverage: 100%" — the previously uncovered line 1011 is now hit. scripts/review-checks.sh --diff PASS on the full diff.

My earlier review had no inline threads, so nothing to resolve. APPROVE at bfd7cf71. Merge is the owner's.

@keweichen
keweichen merged commit 42762fe into main Oct 16, 2026
35 checks passed
@keweichen
keweichen deleted the factory/kc/revalidate-ca5c94bedffe4d950f98987a-ffd97d25ee72 branch October 16, 2026 03:26
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.

3 participants