Matrix-gap sprint: kill mid-run for 6 more runtimes + Cursor/Copilot pre-tool gates - #5009
Matrix-gap sprint: kill mid-run for 6 more runtimes + Cursor/Copilot pre-tool gates#5009vivekchand wants to merge 5 commits into
Conversation
The governance matrix promised Kill for Codex/OpenCode/Aider/Goose, but the shipped cloud Stop button never worked for them: the relay carries no cwd and resolve_by_cwd refuses with no_cwd. Family session rows never persisted cwd either, so there was nothing to backfill from. This PR makes the promised cells true and extends kill/pause to the runtimes recon proved resolvable: - process_control: copilot (pid from ~/.copilot/logs/process-<ms>-<pid>.log, "Workspace initialized: <sid>" body marker; verified live on 1.0.77-1.0.80), qwen_code (qwen-code's own pid sidecar <projects>/<hash>/chats/ <sid>.runtime.json with identity cross-checks instead of its unreliable started_at), pi/grok/deepseek_harness/kimi via argv+cwd. "pi"/"dsh" are exact-basename hints (substring would hit pip/python). Cursor CLI sessions (cursor-agent) resolve by cwd; the IDE stays honestly unsupported. - sync.py _run_process_control: backfill cwd from the session row (new local_store.get_session_location) when the relay omits it; consult approvals.KILL_HANDLERS first so the pro-registered n8n / antigravity / nanoclaw API kills fire from the Stop button, not only on approval deny; route nemoclaw through the openclaw CLI cancel instead of a guaranteed runtime_not_signal_supported. - family ingest: persist cwd/git_branch on session rows (alias walk incl. goose workingDir) and stamp events.workspace_id so approvals' _session_cwd_hint stops returning ''. /cwd1 ingest-rev salt re-ingests existing sessions once to heal the column. - routes/sessions.py emergency stop: family sids now route to the real pid engine instead of writing a .stop file nothing reads and reporting ok:true (silent fake success). Tests: 13 new (copilot E2E kill incl. pid-reuse guard, qwen sidecar identity guards, exact-hint semantics, handler-first dispatch, cwd backfill). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
Visual diffComparing 37 of 66 comparison(s) flagged (>1% pixel diff).
Folder: 032d8b82a55c. Full PNGs also attached as a workflow artefact. Generated by visual-diff bot. Pixel diffs >1% flagged; eyeball the table before merging. This check is non-blocking — fail = bot bug, not a code problem. |
…ol gates)
The matrix's "Block before it runs" column was Claude Code-only. Both Cursor
and Copilot CLI ship native hook systems that can DENY a tool call before it
executes (verified live 2026-08-19: Cursor 3.16 hooks.json beforeShellExecution
deny surfaces to the model; Copilot CLI 1.0.80 preToolUse deny blocked a shell
command with our reason). This wires them into the SAME approvals engine the
Claude Code PreToolUse gate uses:
- clawmetry/runtime_gates.py (new, stdlib-only): cursor + copilot gate
installers (GATE_HANDLERS entries driven by sync_runtime_gates) and hook
clients. Cursor: non-destructive merge into ~/.cursor/hooks.json on
beforeShellExecution + beforeMCPExecution (+ beforeReadFile when a policy
covers reads), marker-owned entries only, state-file uninstall. Copilot:
we own the whole $COPILOT_HOME/hooks/clawmetry.json file (Copilot loads
every hooks/*.json), so install/uninstall can never clobber foreign
entries. Fail-open contract preserved end to end: the clients always exit
0 and print nothing on failure (= "no opinion").
- routes/hooks.py: the pretooluse receiver is now per-runtime
(/api/hooks/<slug>/pretooluse for claude-code|cursor|copilot) so a Cursor
pause is filed as a cursor approval, not a claude_code one; and policies
are runtime-scope filtered (a policy pinned to claude_code no longer
gates other runtimes), mirroring sync_runtime_gates.
- cli.py fast path: `clawmetry hook cursor|copilot --base <url>` (stdlib-
only, translates the runtime's payload/response shapes: Cursor
{"permission": ...}, Copilot {"permissionDecision": ...,
"permissionDecisionReason": ...}; Copilot toolArgs arrives as a JSON
string and is parsed).
- hooks_installed.json markers for cursor/copilot so the reactive watcher
never double-files approvals for calls the hook already paused.
Tests: 13 new (installer merge/idempotence/uninstall-only-ours, risk-policy
read-event, whole-file copilot ownership, client payload/response mapping
incl. JSON-string toolArgs, fail-open, per-runtime receiver stamping,
runtime-scoped policy isolation, registry entries).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
Drift-bot on clawmetry-pro#148 flagged that the blueprint wants cwd as a first-class Session field, not only an extra["cwd"] mirror. Add it to the dataclass (default "", exported in to_dict) and prefer it in the family upsert over the metadata alias walk. Adapters keep mirroring into extra["cwd"] while pre-cwd OSS wheels are in the fleet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
| # | ||
| # copilot (GitHub Copilot CLI) has a claude_code-grade strong resolution: each | ||
| # run writes ``~/.copilot/logs/process-<epoch_ms>-<pid>.log`` whose body logs | ||
| # ``Workspace initialized: <session_id>`` — pid comes from the FILENAME and the |
There was a problem hiding this comment.
The PR extends kill/pause support to Cursor CLI sessions (resolve_cursor function at line 1102+), but the blueprint and codebase documentation consistently state that Cursor is intentionally unsupported because one IDE process holds every session. The documentation comment previously stated cursor is "explicitly UNSUPPORTED for per-session signals"; now CLI sessions are supported but IDE sessions remain unsupported.
| if info.get("ok") or not cwd: | ||
| return info | ||
| return resolve_by_cwd(runtime, cwd) | ||
| if runtime == "qwen_code": |
There was a problem hiding this comment.
The requirement AC-GOV-ERS-001.3 specifies that unavailable runtimes must not be presented as available. The PR adds Cursor to SUPPORTED_RUNTIMES (line 90-91) but Cursor IDE sessions remain unsupported — only CLI sessions are killable, creating a split implementation where the same runtime is both supported and unsupported depending on session type.
|
| # | ||
| # kimi / pi / grok / deepseek_harness are per-terminal CLI processes resolved | ||
| # by argv+cwd like codex; "pi" and "dsh" are exact-basename matches (see | ||
| # _EXACT_ARGV_HINTS) because substring matching would hit pip/python or any |
There was a problem hiding this comment.
Cursor added to SUPPORTED_RUNTIMES with clipboard note stating "cursor is omitted on purpose," but the blueprint specifies Cursor IDE sessions remain unsupported. Only CLI sessions should resolve; the implementation adds Cursor to the supported set without distinguishing IDE vs CLI sessions in SUPPORTED_RUNTIMES.
| @@ -0,0 +1,491 @@ | |||
| """Pre-tool gates for Cursor and GitHub Copilot CLI — "block before it runs". | |||
There was a problem hiding this comment.
The new runtime_gates.py file implements pre-execution tool gates for Cursor and Copilot CLI, but this implementation is not mentioned or documented in the "Governance Policy and Approval" blueprint. The blueprint should document the runtime-gates module and its role in the gating workflow.
|
|
||
|
|
||
| @bp_hooks.route("/api/hooks/<slug>/pretooluse", methods=["POST"]) | ||
| def api_hook_runtime_pretooluse(slug): |
There was a problem hiding this comment.
The _pretooluse_impl function now applies runtime-scoped policy filtering, but the blueprint's "Governance and Human Approval" states that #AttentionHookReceiver "MUST NOT fail in a way that could stall the calling runtime." The new generic handler filters policies by runtime before matching, potentially introducing new failure modes not present before runtime gating was added.
|
Drift-bot triage (6 findings, round 2):
Separately: every other check on this round died in 2s with "account is locked due to a billing issue" — GitHub Actions billing needs a human fix before CI can rerun (same lock is blocking the 0.12.738 publish per the fleet notes). |
|
Live E2E of the Copilot gate on a real machine (2026-08-20, Copilot CLI 1.0.80, real credits spent):
Cursor's deny path was separately verified live during recon (a |
|
blocked on author decision — skipping (auto-mergeability sweep) The latest CI round (run IDs 32315217XXX, starting 23:54:49 UTC) failed in 2 seconds across all checks — consistent with the billing lock you described. The initial CI run (visual-diff screenshots generated at 22:21 UTC) ran to completion. No mechanical code fix available here; the block clears once the billing issue is resolved and a fresh CI run completes. Generated by Claude Code |
|
| # | ||
| # copilot (GitHub Copilot CLI) has a claude_code-grade strong resolution: each | ||
| # run writes ``~/.copilot/logs/process-<epoch_ms>-<pid>.log`` whose body logs | ||
| # ``Workspace initialized: <session_id>`` — pid comes from the FILENAME and the |
There was a problem hiding this comment.
AC-GOV-ERS-005.2 specifies Cursor support must be per-session (CLI stoppable, IDE unsupported) and states "A split runtime is never labeled wholly available." However, the code adds "cursor" to SUPPORTED_RUNTIMES as a blanket classification, failing to distinguish session type at the set level.
| # (process_control.resolve_by_cwd); adapters should ALSO mirror it into | ||
| # extra["cwd"] while older OSS wheels without this field are in the | ||
| # fleet (a pro adapter passing cwd= against an old wheel would crash). | ||
| cwd: str = "" |
There was a problem hiding this comment.
The Session dataclass adds cwd as a first-class field, documented as required for kill/pause pid resolution. However, the blueprint's section on session location (ADR-010) describes cwd as extracted and normalized at ingest, not as a first-class Session field that adapters must populate.
|
Merge blocker: drift-bot (4 findings) The What needs updating: To fix:
Generated by Claude Code |
What
The public capability matrix claims Kill mid-run ✓ for Codex/OpenCode/Aider/Goose — but the shipped cloud Stop button never worked for them: the relay carries no
cwd, the daemon never backfilled it, andresolve_by_cwdrefused withno_cwd. Family session rows never persistedcwdeither. This PR makes the promised cells true and extends kill/pause to every runtime the 2026-08-19 recon proved resolvable.process_control
~/.copilot/logs/process-<epoch_ms>-<pid>.log(Workspace initialized: <sid>body marker) — pid from the filename, epoch_ms doubles as the pid-reuse-guard start token. Verified live on Copilot CLI 1.0.77–1.0.80 (SIGTERM is graceful:session.shutdownwritten,--resumeworks after).<projects>/<hash>/chats/<sid>.runtime.json({pid, session_id, work_dir}— written explicitly for observability daemons). Itsstarted_atis the write time, so the resolver uses identity cross-checks (argv contains qwen + live cwd == work_dir) instead of the start-token guard.pi/dshare exact-basename hints (substring would hit pip/python/any path containing "dsh").cursor-agent) sessions resolve by cwd — one process tree per session; the IDE stays honestly unsupported.sync.py daemon dispatch
cwd, look it up from the session row (newlocal_store.get_session_location) — fixes the broken codex/goose/opencode/aider path end-to-end.runtime_not_signal_supported.family ingest
cwd/git_branch(alias walk, incl. gooseworkingDir); family event rows stampworkspace_idsoapprovals._session_cwd_hintstops returning''./cwd1ingest-rev salt re-ingests existing sessions once to heal the column.local emergency stop honesty
/api/sessions/<sid>/stopfor family sids routed to the real pid engine instead of writing a.stopfile nothing reads and returningok:true.Tests
13 new: copilot E2E kill incl. pid-reuse guard refusal, qwen sidecar identity guards, exact-hint semantics, handler-first dispatch, cwd backfill, nemoclaw routing. Full
test_process_control.py(33) +test_sync_process_control_dispatch.py(13) green.Companion PRs: clawmetry-pro (adapter cwd promotion + Copilot $0.01/credit + QM cost) and clawmetry-cloud (gate lists + dialog copy).
🤖 Generated with Claude Code