Skip to content

feat(app-control): add a threads capture kind with py-spy dump - #9542

Merged
kyle-sexton merged 7 commits into
mainfrom
feat/9532-threads-kind
Oct 24, 2026
Merged

kyle-sexton merged 7 commits into
mainfrom
feat/9532-threads-kind

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Closes #9532

Summary

/app-control:drive gains a threads capture kind for a hung process, starting with Python through py-spy dump, bound to the pid this run launched. /debugging:analyze-profile reads the resulting thread dump directly, with no SQLite step, from a new format page. The frontmatter of both skills is unchanged; the analyze-profile description keeps its wording (slice plan Q16, option a).

Fix

  • Drive SKILL.md. threads joins the kind list and the Actions row. A new ## Pid binding section:
    • The launch step records pid, start time and command line in report.json launch.
    • Before any command that touches the pid, the shown script included, drive re-reads all three and goes on only when they match.
    • A wrapper resolves to its one child by parent pid. No child, several children, a mismatch or a vanished pid is blocked: launched process no longer matches.
    • A name lookup appears only in the "never" sentence.
  • Drive Rules. A threads capture always takes the existing elevated route: a shown script when interactive, blocked: capture needs elevation when unattended. Unattended, it stops there and runs no attempt, pid recheck or lookup. The upstream root rule is a pointer to the py-spy README section When do you need to run as sudo? (as of 2026-10-23; recheck on a py-spy major release).
  • Drive routing. ## Next routes threads artifacts to analyze-profile.
  • control-skill.md. threads is added to the hub's Capture kinds. In the hang row, Python names py-spy dump and every other runtime reads "not yet". A note says a threads command takes a <pid> placeholder and no elevation prefix.
  • evidence.md. Adds the launch field, and capture steps record the process start time.
  • analyze-profile.
    • The format table links the new thread-dump.md. The page covers where the reading begins (threads in the same frame, lock waits, the main thread), when the search is done, and how one dump settles nothing on its own. It applies the existing data and redaction rules to thread names and the command line.
    • The SQLite and cleanup steps now carve out a direct read.
    • "No artifact yet" names --kind threads.
  • Evals. One new case per skill, in each evals.json and as a plugin eval case:
    • plugins/app-control/evals/threads-capture-blocked-unattended/, the app-control plugin's first plugin eval case.
    • plugins/debugging/evals/analyze-profile-reads-thread-dump/, which carries judge samples.
  • Changelog. One fragment per plugin.

Verification

  • Fixture. It is real py-spy output from a deadlocked toy script, a lock-order cycle shipped as evals/fixtures/inventory/sync.py.txt. Recorded with py-spy 0.4.2 and CPython 3.14.4 on Linux (WSL2, ptrace_scope 1), with no root, because the recorder execs into the app's parent:

    sh -c '/usr/bin/python3 inventory/sync.py & sleep 2; exec py-spy dump --pid $!'
  • Acceptance checks.

    • rg -n threads finds lines in both drive files.
    • thread-dump.md exists, and the SKILL.md table links it.
    • check-evals-quality.sh reports PASS with 0 warnings on both evals.json files.
    • grep -nE '#[0-9]{3,}' and grep -nE '5031|SETWorks|Blazor' over the changed skill and eval files return nothing.
    • Frontmatter diff against origin/main is empty.
    • check-skill.sh: drive PASS (0 errors, 4 warnings: the existing body-growth and style warnings); analyze-profile PASS (0 errors, 3 warnings).
    • Also passing: check-changelog-fragments.sh --check, check-spoke-plugin-root.sh, markdownlint, shellcheck and shfmt on the scaffolds.
  • Judge calibration. names-the-cycle with an opus judge agreed with its 3 labeled samples on 9 of 9 runs (100%).

  • Plugin evals (claude plugin eval, Claude Code 2.1.310, judge opus, 3 runs, one --case per invocation with --scaffold, TMPDIR under the worktree's .work/). Each before/after pair ran concurrently against plugin copies outside any git repository; "before" is origin/main with the new case added. Final round, after the review fixes:

    Case Before After Validity gate
    analyze-profile-reads-thread-dump (with/without ablation) with 0.92, without 0.75 with 1.00 (3/3 pass), without 0.75 (delta 0.25) VALID both
    threads-capture-blocked-unattended (--ablation none) 0.60, 0/3 pass: every run rechecked the pid through ps or /proc and wrote no blocked: capture needs elevation for threads 1.00, 3/3 pass before INVALID (4 sandbox denials on /proc reads, which is the behavior the case forbids); after VALID

    The noise report says "n too small to call" (1 case each), so no gain is claimed beyond these numbers. The drive case runs with --ablation none because every without-arm run hit sandbox denials on /proc reads, which makes a with/without pass INVALID. Judge calibration for names-the-cycle (opus): 9 of 9 runs agree with its 3 labeled samples, re-run after its criteria changed.

  • Ready reviews. Round 1 (code and security, fresh context) findings fixed in this PR: launch record as data with an integer-only pid; owner uid and exact raw start time in the recheck; the shown script repeats the recheck before its one recorder command; wrapper child fields; the secrets mark on reports that hold a command line; graders matched against the JSON-serialized command and the written report; no restated py-spy layout. Kept as follow-ups: pid binding for the other pid-targeting kinds (cpu, heap) and for cleanup's stop; the JVM, .NET, Go and native hang-row pointers stay for those rows' slices; an analyze-profile description change goes through invocation probes (slice plan Q16).

  • Round 2 and 3 reviews. Two safety defects were fixed in this PR, each followed by one code and security review of its own delta and a fix for what that review found in the two fixes:

    • Literals only. The shown root script now carries only integers and a sha256 of the NUL-separated /proc/<pid>/cmdline bytes, never command-line text. It rechecks every uid field, the start time and the hash as positive matches, exits 3 on any difference or failed read, and remaps a recorder exit of 3. The yes is asked beside the live command line.
    • Wrapper child. Its uid is recorded and must be the invoking user's, so a root-owned child is blocked. Its values are read fresh from the process table, and the recorder targets it.
    • Everything else from rounds 2 and 3 moved to #9546.

Related

🤖 Generated with Claude Code

kyle-sexton and others added 4 commits October 23, 2026 21:39
Drive gains a threads capture kind for a hung process, bound to the pid
this run launched: the launch record keeps pid, start time and command
line, all three are rechecked before any command touches the pid, and a
wrapper resolves to its child by parent pid, never by a name lookup. A
threads capture always takes the elevated route. analyze-profile reads
a thread dump directly from a new format page with no SQLite step.

Closes #9532

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…echeck

The unattended route runs no pid recheck or process lookup, since nothing
will touch the pid. The eval's no-recorder grader now matches a recorder
or elevation command at command position, not the word in report text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the ready reviews: the launch record is data and only an integer
pid enters a command; the recheck adds the owner uid and an exact raw
start time; the shown script repeats the recheck before its one
recorder command; wrapper children record command line and parent pid;
reports holding a command line carry the secrets mark. The thread-dump
page points at py-spy instead of restating its layout. Eval graders now
match the JSON-serialized command, read the written report, forbid any
unattended process lookup, and check for a second-dump recommendation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 24, 2026 02:19
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T02:21:12.038678Z 9b1115b Draft marked ready
ℹ️ 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9b1115b7cc

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/app-control/skills/drive/SKILL.md Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

claude-security-review: 1 inline finding(s) on 9b1115b7cc22c570f6a068997b822ffea92320d8; the summary is in this lane's pull request comment.

claude-lane: claude-security-review

Comment thread plugins/app-control/skills/drive/SKILL.md Outdated
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed 1469acb09830d175bba3ff9da70b5b430fdedca4: 0 finding(s), 0 as inline comments.

Incremental security review of 1469acb against 1e1fea6: no security issues found.

The change is two doc wording edits, in plugins/app-control/skills/drive/SKILL.md and reference/evidence.md. Both now say the command_line_sha256 input is every byte of /proc/<pid>/cmdline as read, with the NUL separators and trailing NUL included. That makes the hash input exact. It does not delete, narrow or soften any rule, so it does not trigger the instruction-surface lens. The pid-binding recheck is unchanged: uid fields, start time and hash must all still match. No manifest files changed, so no manifest <field> lines apply.

The PR description (data only) contained no embedded instructions.

I did not check that the shown root script computes its hash over the same bytes, because it is outside this increment and the earlier review covered it.

critical-findings: 0

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

claude-review: 1 inline finding(s) on 9b1115b7cc22c570f6a068997b822ffea92320d8; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread plugins/app-control/skills/drive/SKILL.md Outdated
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed 1469acb09830d175bba3ff9da70b5b430fdedca4: 0 finding(s), 0 as inline comments.

Incremental review of 1e1fea6..1469acb: no findings.

The change is documentation only. It makes the command-line hash input explicit in plugins/app-control/skills/drive/SKILL.md and plugins/app-control/skills/drive/reference/evidence.md. Both now say the hash covers every byte of /proc/<pid>/cmdline as read, NUL separators and trailing NUL included.

  • Consistency: I grepped the app-control plugin and the full PR diff for other descriptions of the hash input. The two edited spots are the only ones, and they agree with each other. They also agree with SKILL.md's rule that the stored command_line is a display form and never the hash input. The PR body still says "NUL-separated" for the shown script, which is compatible but less precise than the new wording.
  • Closing issue feat(app-control): add a threads capture kind with py-spy dump #9532: no acceptance criterion depends on the hash input's byte form, so none is affected.
  • Not checked: I ran nothing. Whether the shown root script's sha256 computation actually includes the trailing NUL is not visible in this diff, so I did not check it. The hash appears only in prose.
  • Hold: none of the hold classes (cost, trust-gate, critical-security) apply.

kyle-sexton and others added 2 commits October 23, 2026 22:23
The shown script now carries only the pid, uid, raw starttime integer and
a sha256 of the command line, recomputes and compares them, and exits 3
on a difference before the recorder runs; it never embeds the command
line text. The wrapper child's uid is recorded and must be the invoking
user's, so a root-owned child is blocked. The drive fixture's launch
record carries the uid, the starttime integer and the hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The script's exit 3 now also covers a failed or empty read and a missing
hash tool, a recorder exit of 3 is remapped, and comparisons are positive
matches. Every uid field must match. The hash input is named (the
NUL-separated /proc/<pid>/cmdline bytes), the yes is asked beside the
live command line, and the wrapper child's literals, target pid and
fresh-read rule are stated. The pid must be positive again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

claude-review: 2 inline finding(s) on 1e1fea60950b1b486f4d1ea8d85e796630ac71d4; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread plugins/app-control/skills/drive/SKILL.md
Comment thread plugins/app-control/skills/drive/SKILL.md
The hash covers every byte of /proc/<pid>/cmdline as read, so the
recorded hash and the script's recompute agree on the trailing NUL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit d90fa9d Oct 24, 2026
36 checks passed
@kyle-sexton
kyle-sexton deleted the feat/9532-threads-kind branch October 24, 2026 03:27
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.

feat(app-control): add a threads capture kind with py-spy dump

1 participant