Skip to content

feat(app-control): fill the threads capture row for JVM, .NET, Go and native #9490

Closed

Description

@kyle-sexton

Scope now (narrowed 2026-10-27)

#9532 / #9542 shipped the threads kind with py-spy dump, the analyze-profile direct-read branch, the elevated route and the ## Next routing. Pid-binding and eval follow-ups are #9546. This issue now covers only the remaining Format rows below: the JVM, .NET, Go and native (gdb) cells, and Node.js n/a. The decision record in the comments carries the criteria. Correction to that record: the browser-page cell and macOS (sample) are out of scope; leave them as they are.

Problem

/app-control:drive has no capture kind for a hung process, a stack sample of every thread. Its control-skill table maps jank and hang to the one trace kind, but they use different tools per runtime: for example JFR for jank and jcmd thread dump for hang. That breaks drive's one-tool-per-kind rule. PR #9328 left the hang row as a capability gap after four review rounds on a threads kind kept finding gaps (commits df1a6eb through 7d2a3f4 on that branch hold the attempt).

What a threads kind needs (from those reviews)

  • Analysis path: analyze-profile's flow sends every artifact to an exporter and then queries SQLite (analyze-profile/SKILL.md:60-62). A thread dump is text read directly, so the skill needs a direct-read branch for it, with an eval that reads a real dump.
  • Pid binding: a pid-targeting tool (py-spy dump, jcmd, dotnet-stack, gdb) may capture only the pid this run launched. Drive must record that pid and check it before each capture. A name lookup such as pgrep -f node could otherwise reach a process the person owns, unattended included.
  • Elevation: a launched-process capture that needs elevation goes through drive's shown-script route, and needs an eval.
  • Format rows: py-spy dump, jcmd thread dump, dotnet-stack, goroutine dump and gdb thread apply all bt, each with an anchored manual pointer, an as-of date and a recheck trigger. Node.js has no file-writing all-thread dump, so its cell is n/a. A Go dump through SIGQUIT exits the process, so prefer debug/pprof/goroutine?debug=2.
  • Routing: drive's ## Next sends a threads artifact to analyze-profile.

Activity

  1. added
    priority: mediumReal value, no hard deadline; normal backlog flow.
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    on Oct 23, 2026
  2. github-actions commented on Oct 26, 2026

    @github-actions
    Contributor

    The triage sweep dispatched intake triage for this issue (attempt 1 of 2).

  3. github-actions commented on Oct 26, 2026

    @github-actions
    Contributor

    Proposed work class: work-class: scoped
    Basis: automated intake triage; the lanes App applied this class.

    Size: Small
    Complexity: Low
    Uncertainty: Known
    Flags: none
    Blocked by: none

    I read this as: fill the remaining threads (hang) cells in plugins/app-control/reference/control-skill.md. The JVM, .NET and gdb cells already read as filled on main, each with a manual pointer, as-of date and recheck trigger. The Go cell still reads "not yet". It needs the goroutine dump command (debug/pprof/goroutine?debug=2) and its pointer, as-of date and recheck trigger. Beyond that, check the .changes/app-control/ fragment and the "not yet" in the macOS half of the Native cell. The narrowed scope leaves macOS and the browser page as they are.

    Nothing is missing. I found no duplicate. Pid binding, elevation and evals stay with #9546.

    Automated triage, applied by the lanes App.

  4. added
    work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.
    status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.
    on Oct 26, 2026
  5. melodic-automation-lanes commented on Oct 26, 2026

    @melodic-automation-lanes
    Contributor

    Not admitted to the issue lane: no trusted-human acceptance criteria: a listed human must put the confirmed - [ ] criteria in the body or post them as their own comment, or, on an issue a listed human filed, intake's newest refinement comment must hold them unedited. Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).

  6. melodic-automation-lanes commented on Oct 26, 2026

    @melodic-automation-lanes
    Contributor

    The latest triage verdict holds nothing: the issue is at status: ready.

  7. melodic-automation-lanes commented on Oct 26, 2026

    @melodic-automation-lanes
    Contributor

    Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.

    Intended outcome: Close as done for the threads kind with py-spy dump; track the remaining runtime rows (jcmd, dotnet-stack, goroutine, gdb, Node n/a) in a narrower issue if wanted.

    Related work

    Open questions

    1. Should this issue be closed as done by #​9532, with the other runtimes (jcmd, dotnet-stack, goroutine pprof, gdb, Node n/a) split into a new scoped issue?
      RECOMMENDED: Yes. Close it and open a narrower issue for the per-runtime format rows only. Basis: plugins/app-control/reference/control-skill.md:60 shows only py-spy dump filled in and the other runtimes marked 'not yet'; plugins/app-control/CHANGELOG.md:61 says the other runtimes read 'not yet'.
    2. Is the pid binding and eval work in #​9546 enough to cover the pid-binding and elevation bullets here?
      RECOMMENDED: Yes. Leave those bullets to #​9546. Basis: plugins/app-control/skills/drive/SKILL.md:133-152 already has the pid binding and the elevated route; #​9546 is open for the remaining gaps.

    Sources read

    • plugins/app-control/skills/drive/SKILL.md:12,35,133-152,233: the threads kind exists, binds to the launched pid, always takes the elevated route, and ## Next routes it to analyze-profile
    • plugins/app-control/reference/control-skill.md:60: the hang row has py-spy dump for Python and 'not yet' for every other runtime
    • plugins/app-control/CHANGELOG.md:61-67: records the py-spy threads capture, the unattended-block eval and the Next route
    • plugins/debugging/skills/analyze-profile/SKILL.md:60-63: has a direct-read thread dump branch with no SQLite step
    • plugins/debugging/skills/analyze-profile/evals/evals.json:46: has an eval that reads a real py-spy dump

    Automated refinement by the lanes App. Answer with a comment whose first line is **Decision recorded.**.

  8. changed the title [-]feat(app-control): add a threads capture kind for hung processes[/-] [+]feat(app-control): fill the threads capture row for JVM, .NET, Go and native[/+] on Oct 27, 2026
  9. 5 remaining items

  10. melodic-automation-lanes commented on Oct 27, 2026

    @melodic-automation-lanes
    Contributor

    The lanes App's pull request #10497 merged with Refs:, so this issue stays open and nothing starts the lane on it again. work-unblock removed status: ready and agent-ready, so the hourly triage sweep sends it back to intake triage, which decides what work is left and admits it again.

  11. added
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    and removed
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Oct 27, 2026
  12. melodic-automation-lanes commented on Oct 28, 2026

    @melodic-automation-lanes
    Contributor

    Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.

    Intended outcome: Fill the remaining Go cell of the hang (threads) row in the control-skill reference with the pprof goroutine dump, add its manual pointer, as-of date and recheck trigger, and add a changelog fragment.

    Acceptance criteria

    • plugins/app-control/reference/control-skill.md row 'A process that stops responding (hang)' has a Go cell that names the goroutine dump through the net/http/pprof goroutine?debug=2 endpoint, replacing 'not yet'.
    • The Go cell uses no SIGQUIT and no elevation prefix, and carries no flag list or output format copied from the manual.
    • The Go manual entry in the Manuals list (the net/http/pprof bullet) gives an anchored pointer covering the goroutine dump, an as-of date of the day of the change and a recheck trigger.
    • The Node.js and browser page cells of the hang row stay unchanged: n/a for Node.js and 'not yet' for the browser page.
    • The macOS half of the Native cell stays 'not yet' and the macOS sample manual note is not edited.
    • The JVM, .NET and Linux gdb cells and their pointers are not edited; #​10497 already filled them.
    • A new changelog fragment under .changes/app-control/ records the change, following the naming and format of the existing fragments in .changes/*/.
    • The repository's markdown and docs checks pass for the edited files.

    Dependencies

    • #​9546 holds the pid-binding, elevation and eval follow-ups; this issue does not wait on it.

    Related work

    Sources read

    • plugins/app-control/reference/control-skill.md:63: the hang row has JVM, .NET and Linux gdb cells filled; Go reads 'not yet', as does the browser page and the macOS half of Native.
    • plugins/app-control/reference/control-skill.md:108: the Go manual entry points at net/http/pprof Usage_examples, as of 2026-10-23, recheck on a Go minor release.
    • plugins/app-control/reference/control-skill.md:72-75: threads commands use a <pid> placeholder and no elevation prefix.
    • .changes/: no app-control fragment is present on main now, so a new one is needed (other modules use <type>-<slug>-<hash>.md).

    Automated refinement by the lanes App. Admission counts only criteria a listed human wrote: to confirm these, post them as your own comment whose first line is **Decision recorded.**, which sends the issue back through intake.

  13. melodic-automation-lanes commented on Oct 28, 2026

    @melodic-automation-lanes
    Contributor

    The work-resolve-issue run that agent-ready started failed or was cancelled before it reported, or it stopped or the token broker refused it because its workflow files differ from main's. work-unblock removed and added agent-ready back to start a fresh run on main's workflow files. If this run ends the same way, it re-fires the issue again, waiting longer each time.

  14. added
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    and removed
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Oct 28, 2026
  15. melodic-automation-lanes commented on Oct 28, 2026

    @melodic-automation-lanes
    Contributor

    The work-resolve-issue lane stopped partway through this issue and needs a person.

    Why, in the run's own words:

    design-change: The only cell this issue still covers is Go, and filling it as the issue asks would change drive's pid-bound threads design. The JVM (jcmd &lt;pid&gt; Thread.print), .NET (dotnet-stack report -p &lt;pid&gt;), Linux native (gdb -p &lt;pid&gt; -batch -ex 'thread apply all bt') and Node.js n/a cells already shipped in app-control 0.15.0 (plugins/app-control/reference/control-skill.md:63, CHANGELOG.md:25). The issue prefers debug/pprof/goroutine?debug=2 for Go. That dump is an HTTP response from a server the app starts itself, at an address the app picks, with no pid to target. The contract requires a threads command with a &lt;pid&gt; placeholder (control-skill.md:72-75), and drive runs a threads recorder only through build_capture_script.py, which rechecks the launched pid (drive/reference/pid-binding.md:45-57). Rechecking the pid does not prove which process answers on that port, so the binding would not hold. 0.15.0 left Go as "not yet" for this reason. The other Go option the issue names, SIGQUIT, exits the process. A person must decide: (a) keep Go as "not yet" and close this issue, (b) widen the threads contract so an endpoint-served dump can be bound to the launched process (for example, by checking which pid owns the listening socket), which belongs with #9546's pid-binding work, or (c) name a pid-attaching Go recorder for the cell, such as Delve attach, and accept its trade-offs.

    What the run says it did not do:

    • The Go cell of the threads row and its changelog fragment: not written, because the issue's preferred source (pprof goroutine?debug=2) conflicts with the pid-bound threads contract (see reason)
    • tidy: skipped, since there is no change to tidy
    • review, verify and checks prep steps: skipped, since there is no diff

    Run: https://github.com/melodic-software/claude-code-plugins/actions/runs/40628910072

    To retry, remove needs-human and add agent-ready again.

  16. kyle-sexton commented on Oct 29, 2026

    @kyle-sexton
    MemberAuthor

    Re-triage: done, with Go left as "not yet" by design. This takes option (a) from the lane's stop (#9490 (comment)). Option (a) is the only one that keeps drive's pid-bound threads design. Option (b) would change that design and option (c) would change the capture contract, so neither fits this issue.

    Shipped: the JVM (jcmd <pid> Thread.print), .NET (dotnet-stack report -p <pid>) and Linux native (gdb -p <pid> -batch -ex 'thread apply all bt') cells, plus Node.js n/a. They landed in #10497 (merged 2026-10-27, e278f34) and are at plugins/app-control/reference/control-skill.md:64 on main at 489fb55. The macOS half and the browser page were already out of scope (see "Scope now" in the body).

    Why Go stays "not yet":

    • goroutine?debug=2 is served by an HTTP endpoint the app starts, at an address the app picks. Nothing binds it to the launched pid, and drive allows a threads capture only through the pid recheck (plugins/app-control/skills/drive/reference/pid-binding.md:44-57).
    • SIGQUIT prints the dump but exits the process.
    • Delve can attach by pid and detach without killing (dlv attach <pid>, then goroutines -t and exit -d: https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_attach.md and https://github.com/go-delve/delve/blob/master/Documentation/cli/README.md#exit). Run without a terminal, though, it needs those commands on stdin (--allow-non-terminal-interactive) or in an --init file. The capture contract allows only the <pid> and output <file> placeholders (control-skill.md:73-75), and the builder passes the command as plain arguments (pid-binding.md:48). An input file would be a contract change.

    Reopen if drive's capture contract gains an input-file placeholder, or if Go ships a pid-targeted goroutine dump that writes to stdout or a file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.priority: mediumReal value, no hard deadline; normal backlog flow.status: readyTriaged, unblocked, and fully specified; eligible to pick up.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Fields

    No fields configured for Feature.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions