Skip to content

fix: address remaining agent plugin verification bugs (B5-B12) - #14

Open
jbachorik wants to merge 18 commits into
mainfrom
claude/agent-plugins-b5-b13
Open

jbachorik wants to merge 18 commits into
mainfrom
claude/agent-plugins-b5-b13

Conversation

@jbachorik

@jbachorik jbachorik commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

What & why

Fixes B5–B12 from the verification of https://btrace.io/agent-plugins/, one commit each:

  • B5: jfr-analyzer named a JBang alias that doesn't exist; it now says jfr-mcp@btraceio.
  • B6: the failure skill printed exception messages, against the data-safety promise. The default probe now prints the type only. It also warns that jstack() and the oneliner stack action omit the probed method, which made one live run name the wrong method.
  • B7: list_probes called detached probes "Active" and hid attached ones. main's fix(mcp): list the MCP server's own probe and close the list_probes client #19 has since rewritten it to list this server's connected probe too, so the merge takes that version; this PR keeps only the skill's confirm-the-target rule around it.
  • B8: clearer skill descriptions, a confirm-the-target rule, and 2 new eval cases.
  • B9: perf-engineer now checks ASYNC_PROFILER_HOME and prefers the supervisor.
  • B10: cleanup output now names what it removed and kept. It still deletes the recording, because docs/perf-engineer-design.md specifies that; the alternative was keeping it, which is a design change.
  • B11: removed a skill from the README composition table that has moved plugins.
  • B12: removed the unknown interface field from 2 manifests.

Also included:

  • A fix for a racy test, detachReturnsTheUnreadOutput, and regen-formats.sh, which generates pprof and OTLP sample data.
  • mcp-e2e.yml now builds a pinned BTrace commit (802d169, develop with the v2 heartbeat from feat(core): detect a silently dropped client-agent connection with a v2 heartbeat btrace#998, which main's silent-drop e2e test needs) on PRs and pushes, so upstream changes can't fail unrelated PRs. A new daily run builds develop, and btrace-ref overrides both. The clean JBang step still uses the latest published snapshot on purpose, because that's what users get.

How verified

Gate Command Result
MCP tests ./gradlew clean test e2eTest at 622bd4f (merge of main), BTrace 802d169 BUILD SUCCESSFUL; unit 56 passed, e2e 16 passed; against 79062f8 the silent-drop test fails
E2e repeat e2eTest 6 times, BTrace snapshot 3.0.0-20261101.163203-247 6 of 6 passed
CI, pinned PR run at 91a5ef2, the earlier pin success; log says from BTrace 79062f8
CI, override btrace-ref: develop run at 91a5ef2 success; develop was also 79062f8 at the time
Workflow lint actionlint -shellcheck= -pyflakes= mcp-e2e.yml exit 0
Supervisor PerfEngineerSessionTest.java 4 passed, 0 failed
Repository validate-repository.js 5 plugins, 31 skills
Evals validate-evals.js Eval corpus valid: 15 cases
Tool references check-tool-references.js all 37 referenced tools exist
Contributor gate validate-marketplace.sh Marketplace validation passed.
B9 live (Sonnet) async-profiler 4.6, no asprof on PATH 3 of 3 recorded through the supervisor
B6 live (Sonnet) failure prompt against a test JVM, final skill text 5 of 5 used the Java probe and named the right method and type; 0 order IDs captured
Analyzer flows triage, drilldown, report on generated .pb.gz and .otlp report.json written for both
B13 clean JBang install, snapshot 3.0.0-20261101.163203-247 no blank lines in probe output
Uncompressed pprof (Sonnet) triage, drilldown, report on .pprof, jar built from jafar main (04e21df, includes btraceio/jafar#128) report.json written; finding is Math.random() contention, about 92% of CPU; a pre-fix jar failed with Not in GZIP format
New eval cases (Sonnet) score-evals.js on live responses, read-only tools missing-target-asks-first 3 of 3 PASS; bastion-safe-attach 4 of 5 PASS on fresh runs after fc1938d

Coverage notes

Unit tests cover B10 (B7's code is now main's), and the validators cover the manifests and references. B6, B8 and B9 are skill text, so the evidence is small live samples, not tests. The new eval cases were scored on live responses with scripts/score-evals.js (missing-target-asks-first 3, bastion-safe-attach 5 after its rewording); deliberately wrong answers fail both. The other 13 of the 15 cases were not run.

Not verified / known limitations

  • bastion-safe-attach first failed 3 of 3 on its literal terms, not its advice, so fc1938d changed them to "same OS user" and "detach". The new terms were chosen after reading those answers, so they were scored on 5 fresh runs: 4 passed, and 1 failed because it said to remove the probe without saying "detach".
  • requiredSkills isn't met in those runs, and the scorer doesn't check it: each run loaded one skill (btrace-observability or btrace-runtime-access).
  • The uncompressed .pprof fix isn't in a jafar release yet (latest v0.29.0), so released servers still reject these files. It passed only against a jar built from jafar main.
  • The analyzer flows ran headless and resumed between steps, which restarts the MCP server, so each lost its session id and reopened the file. Interactive sessions weren't tested.
  • jbang --fresh jfr-mcp-latest@btraceio fails with no main class deduced. The cause isn't established, and a stale local snapshot is possible. It's why the jar above was built by hand.
  • btrace-data-safety still didn't load in the B8 samples, so that part of B8 isn't solved.
  • The daily schedule trigger hasn't run, because GitHub only fires schedules from the default branch. It can't run until this merges.
  • CI hasn't run on the new pin 802d169 yet; it passed locally.
  • One earlier CI failure is unexplained: stopDetachedProbeRemovesTheListedProbe on Oct 5, with no failure message available. It didn't recur in the 6 local runs.
  • The clean JBang step still floats, so a bad published snapshot can fail a PR there. It only checks that the server starts and lists its tools.
  • The jstack() frame bug itself is in BTrace; this PR only warns about it.

Breaking changes / migration

cleanup output gains two fields; it removes nothing. The list_probes text change comes from main's #19.

Links

Follows #13.

🤖 Generated with Claude Code

jbachorik and others added 11 commits November 1, 2026 10:58
detachReturnsTheUnreadOutput raced the fake client's probe thread: deploying returns at the status command, and the later output could arrive after the detach. It failed repeatedly in local gates. The test now waits on a latch the fake client releases once that output is sent.

Verified: ./gradlew clean test e2eTest (31 and 13 passed), scripts/validate-marketplace.sh pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The header said 'Active probes' but only detached probes were listed, so with an attached probe running the 'check that no probe remains' step wrongly passed. It now says 'Detached probes' and adds a line when this server holds an attached probe.

Verified: ./gradlew clean test e2eTest (31 and 13 passed), scripts/validate-marketplace.sh pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README promises exception types by default, yet the failure example printed str(error), and a live run following it captured order IDs. The example now prints the class only, and the message needs approved exposure.

Live runs also showed jstack() and the oneliner stack action omit the probed method, which made one run name the wrong failing method. Both skills warn about that, and that jstack(error) prints the message.

Verified: 3 live Sonnet runs captured no order IDs, and 2 of 3 named the right method; the third predates the warning. Direct probes confirmed the stack behaviour.

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

A bastion question often loaded no skill, and a missing target led the agent to guess a JVM from the local list. Sharpen the descriptions, add a 'Before attaching' section, require PID confirmation, and add two eval cases.

Verified: validate-evals (13 cases). Small Sonnet samples: bastion prompt loaded runtime-access in 3 of 3, up from 1 of 3; the pod prompt asked for the target in 3 of 3 and never attached. btrace-data-safety still did not load in these runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
'extensions and permissions' now lives in btrace-development.

Verified: validate-repository pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The help text told users to run jafar-mcp@btraceio, which JBang rejects with 'No alias found'. Use jfr-mcp@btraceio --stdio --attach.

Verified: scripts/validate-marketplace.sh pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the variable set, an agent ran 'which asprof', decided async-profiler was missing and fell back to jcmd JFR, though the supervisor honours it. The skill now checks both and prefers the supervisor.

Verified: 3 live Sonnet runs with async-profiler 4.6 from the GitHub release and no asprof on PATH all recorded through the supervisor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cleanup deletes the recording even at a user's --output path, as the design doc specifies, and keeps the manifest, while reporting only cleaned:true. Report removed_artifact and retained_session_dir, and document it. Deleting stays as designed.

Verified: PerfEngineerSessionTest 4 passed, 0 failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude Code flagged it on every gate run.

Verified: scripts/validate-marketplace.sh pass with no interface warnings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pprof and OTLP paths had no sample data. The script records an eval scenario with async-profiler and converts it; output stays git-ignored. jfrconv writes raw pprof even when named .pb.gz, so the script gzips it.

Verified: triage, drilldown and report completed on the generated cpu_hotspot.pb.gz and cpu_hotspot.otlp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live runs of the failure prompt still chose a oneliner in 2 of 5 runs, because the stack warning sat in btrace-oneliner-triage, which those runs never load. A oneliner cannot print the exception type and its stack omits the failing method, so those runs gave partial answers. Say so in the failure skill itself.

Verified: 5 live Sonnet runs all deployed the Java probe, named process and IllegalStateException, and captured no order IDs. validate-repository and validate-marketplace pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jbachorik
jbachorik marked this pull request as ready for review November 1, 2026 16:29
jbachorik and others added 7 commits November 1, 2026 17:37
The case required "same operating-system user" and "cleanup". The phrase is not in the plugin text, and 3 of 3 live answers with correct advice failed on it. It now requires "same OS user" and "detach".

Verified: the terms were chosen after reading those answers, so 5 fresh Sonnet runs were scored: 4 passed, and 1 failed for saying remove the probe without "detach". Two deliberately wrong answers still fail. validate-marketplace.sh passes.

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

# Conflicts:
#	evals/btrace-observability.json
BTrace develop (afc9a369) rewords the client error to 'Port N is in use by another process ...; choose a free agent port with -p <port>'. The server no longer recognised it, so MCP callers were told to use a CLI flag they do not have, and the e2e test failed. Match both wordings and always name the 'port' argument.

Verified against BTrace origin/develop 79062f80: ./gradlew clean test e2eTest (32 and 13 passed), validate-repository, validate-evals (15 cases), validate-marketplace pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The job built BTrace's default branch, so an upstream push could turn an unrelated PR red: fc1938d, an eval-only commit, failed after BTrace reworded its busy-port error, which looked intermittent. PR and main runs now build a pinned commit. A daily run builds develop, so upstream changes still surface. The btrace-ref input still overrides both.

The clean JBang step still resolves the floating published snapshot, because that is what users get.

Verified: actionlint passes. The pinned commit passed e2e in run 41329018361.

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

main landed the same busy-port fix (#18) as b88819e, so BTraceClient and
BTraceClientTest take main's version. The fake Client keeps this branch's
laterOutputSent latch, which the detach test needs, plus main's comment.

Verified: ./gradlew clean test e2eTest: 32 unit and 13 e2e tests, 0
failed. validate-repository, validate-evals (15 cases),
check-tool-references and validate-marketplace pass.

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

# Conflicts:
#	plugins/btrace-observability/mcp/src/main/java/io/btrace/mcp/tools/ListProbesHandler.java
#	plugins/btrace-observability/skills/btrace-mcp-operations/SKILL.md
…b5-b13

main's #19 rewrote list_probes to list this server's connected probe as
well as detached ones, which supersedes this branch's attached-probe note,
so ListProbesHandler and its ToolHandlersTest assertion take main's
version. The skill keeps this branch's confirm-the-PID rule plus main's
list_probes wording, and the detach test keeps this branch's
laterOutputSent wait.

main's new McpSilentDropE2ETest needs the v2 heartbeat (btraceio/btrace#998),
which the pinned 79062f8 lacks, so the pin moves to develop 802d169.

Verified: ./gradlew clean test e2eTest with BTRACE_JAR built from
802d169: 56 unit and 16 e2e tests, 0 failed (against 79062f8 the
silent-drop test fails). validate-repository, validate-evals (15 cases),
check-tool-references, validate-marketplace and actionlint pass.

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

This branch has not been deployed

No deployments
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.

1 participant