You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Final readiness pass for BTrace 3.0.0. The review itself is in internal/plans/2026-09-19-v3.0.0-final-readiness.md (every claim carries a file:line or a command run with its result). The branch fixes what the review found:
Release tooling could not run.scripts/release.sh and three release.yml steps read/wrote project.version in common.gradle, a line removed in c354f12b; the version lives in the root build.gradle. btrace --version printed (dev) on every Gradle 9 build because Project.exec no longer exists (now providers.exec). Every Gradle job in release.yml now installs the JDK 24 toolchain the agent's java24 source set needs and runs Gradle on JDK 21 (three jobs ran Gradle 9 on JDK 11, which it does not support). Both the script and the workflow still handle 2.2.x release branches, which keep project.version in common.gradle.
JDK-warning release gate now proves once-per-JVM semantics (probe invokes the warning twice) and gates the -Dbtrace.suppressJavaDeprecationWarning=true property; both negative cases were confirmed to fail.
ASM routing ceilingMAX_ASM_MAJOR_VERSION 69 → 71: the bundled ASM 9.10.1 (newest on Maven Central) parses Java 27 class files, so Java 26/27 use the ASM backend and the ClassFile API backend covers Java 28+. ClassFileApiBackendTest uses version-72 fixtures on a JDK 28 EA toolchain (continuous.yml installs 28-ea); selector/backend tests assert the new boundary.
Class-header reads above the ASM ceiling (found by this PR's CI on JDK 28 EA): ClassInfo read JDK classes with ASM and threw Unsupported class file major version 72, so isAssignable failed and CALL probes were dropped. ClassHeaderReader now dispatches like instrumentation does: ASM up to the ceiling, the JDK ClassFile API above it (ClassFileApiClassHeaderReader in the java24 source set, loaded reflectively). The lazy lookups in ClassHeaderReader, LinkerInstrumentor and BackendSelector publish their reference before the "attempted" flag, so a concurrent caller can no longer observe "attempted" with a null reference.
Agent argument lists were truncated.-javaagent:btrace.jar=grant=NETWORK,THREADS granted only NETWORK: the agent splits the whole argument string on commas before reading key=value pairs, so the second name became a bare key. The same applied to deny=, allowExtensions=, denyExtensions= and probes=, all documented in the comma form. A bare token that directly follows one of those keys now continues that key's list (MainTest covers it); help still works as a bare key.
JDK matrix: no lane pins a build any more. Lanes 8/11/17/21/25 and 28-ea are the newest Temurin release of their major from actions/setup-java (check-latest: true); the 27 lane is marked sdkman: true and resolves through scripts/resolve-sdkman-java.sh to the newest 27 GA build SDKMAN lists (27.0.0-oracle until Temurin 27 is published). CI on this PR showed why: SDKMAN retired 27.ea.31-open at 27 GA, rejected 27-open, and its listing (the API and sdk list java alike) carried no Temurin 8/11/21 build at all while the old pins still installed. update-jdk-versions.yml stays and becomes active again as soon as a lane pins a build.
Container images publish to ghcr.io/btraceio/btrace from a new publish-container-images release job (amd64+arm64, three variants, GITHUB_TOKEN, Docker actions pinned to commit SHAs); the btrace Docker Hub namespace belongs to an unrelated account. All image references updated.
Curated release notes.github/release-notes/v3.0.0.md, wired into the release body via body_path.
Docs/tutorials: agent error text and Tutorial Lesson 6 told users to pass --grant to the client (not parsed); download URLs used asset names that never existed; Oracle Contributor Agreement references removed (project is Apache-2.0, no CLA); tutorials 03/08/09 had steps that could not work as written; ClassFile-backend limitations were stale; Lesson 14 claimed embedding a privileged extension "is the grant" (it is not: the fat-agent plugin carries the permissions manifest into the embedded descriptor and the bridge still gates it); Java support statement is now "8–27+" (JDK 27 GA 2026-09-15); SECURITY.md supported-versions table; blog drafts fact-checked.
Related issue
Release plan internal/plans/2026-07-11-v3.0.0-release-plan.md; touches #932 (mitigations only, root cause stays open) and supersedes the pins in #938.
Closes N/A
Scope and compatibility
I identified the affected module(s) and kept unrelated changes out of this PR.
This preserves the supported Java/runtime compatibility tiers, or the change is documented below.
This does not change the masked-JAR layout, class-loader boundary, or wire protocol.
If it does, I updated the relevant architecture documentation and verification plan.
Compatibility or migration notes:
Java 26 and 27 class files are now instrumented by the ASM backend instead of the ClassFile API backend (docs/architecture/InstrumentationBackends.md updated). The agent's "permissions not granted" message now points at agent-side grant= / policy instead of the nonexistent client --grant flag, and multi-value grant=/deny=/allowExtensions=/denyExtensions=/probes= lists on the -javaagent line now take effect in full. Container images move from btrace/btrace (never published) to ghcr.io/btraceio/btrace.
Testing
Unit tests
Integration tests (if applicable)
spotlessCheck
Documentation/link or sample verification (if applicable)
Commands and results:
# JDK 8/11/17/21 (apt), Oracle JDK 24.0.2 / 26.0.2.1 / 27 GA (27+35) as toolchains
./gradlew spotlessCheck :btrace-dist:build BUILD SUCCESSFUL
./gradlew build BUILD SUCCESSFUL (agent 1019, core 315, client 75, compiler 70, runtime 27 tests, 0 failures)
./gradlew :btrace-dist:releaseSmoke release smoke PASSED (7 sections)
./gradlew -Pintegration :integration-tests:test (JDK 21 target) 72 tests, 0 failures, 2 skipped
TEST_JAVA_HOME=<jdk27> ... :integration-tests:test (JDK 27 GA) 72 tests, 0 failures, 2 skipped — with the Java 27 ceiling, i.e. ASM backend on major-71 classes
TEST_JAVA_HOME=<jdk26> ... --tests ClassFileApiTests --tests PreparedModeAuthenticationFunctionalTest 4/4, 6/6
scripts/verify-jdk-warning.sh on JDK 8, 11, 17, 21, 24, 26, 27 all PASSED (8/11 warn once, 17+ silent; suppression pass green)
DRY_RUN=true scripts/release.sh minor HEAD Release Version v3.0.0, release/3.0._, next 3.1.0-SNAPSHOT
markdown link check over README/docs/docker/.github 0 broken
:btrace-agent:test MainTest / ClassHeaderReaderTest / BackendSelectorTest 11/11, 5/5, 4/4 (review-driven fixes)
Not verifiable in the authoring environment (no JDK 28 EA or Docker reachable): classFileApiBackendTest on JDK 28 EA and the GHCR publish job (skipped on dry runs; first exercised by the real release). CI on the final code head db2abfe5 (run 35471638858) is green on every lane: Temurin 8.0.504+1, 11.0.32+101, 17.0.20+101, 21.0.12+101, 25.0.4+101, Oracle JDK 27+35 via SDKMAN 27.0.0-oracle, and Temurin 28-ea; the ClassFile API backend tests ran on JDK 28 EA in the build job. Copilot review on that head reports no findings; all eleven review threads are resolved.
Documentation and release impact
User-facing documentation is updated, or no documentation change is needed.
Release notes/changelog are updated, or no release-note entry is needed.
Samples, distribution contents, or published coordinates are updated if affected.
This change is safe to merge independently of a release, or the dependency is explained below.
Release notes / follow-up work:
Release 3.0.0 with ./scripts/release.sh minor (develop is already 3.0.0-SNAPSHOT; major would produce 4.0.0). Run one dry_run: true workflow first; the Release workflow has never run.
One-time after the first release: make the btrace GHCR package public.
Drop the sdkman: true entry for the 27 lane once actions/setup-java can serve Temurin 27.
…mon.gradle
c354f12 moved the project version into the allprojects block of the root
build.gradle and removed project.version from common.gradle, but
scripts/release.sh and three steps of the release workflow (dry-run version
update, develop snapshot bump, release-branch snapshot bump) still grepped and
sed-ed `project.version` in common.gradle. Since that line no longer exists,
release.sh aborts with "Cannot find version in common.gradle" and the workflow
bumps would no-op and then fail on an empty commit.
Point both at build.gradle with an anchored `^\s*version = '` pattern so only
the allprojects version line is matched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
…e, fix stale docs
- common.gradle: Gradle 9 removed Project.exec, so getGitCommit() always fell
back to 'dev' and every 3.0.0 build printed "BTrace v.3.0.0 (dev)" with
Build-Revision: dev. Use providers.exec.
- scripts/release-gates/JdkWarningProbe.java + verify-jdk-warning.sh: invoke
the warning twice so "exactly one warning" proves the once-per-JVM guard, and
add a suppressed run so -Dbtrace.suppressJavaDeprecationWarning=true is gated
too. Both negative cases were confirmed to fail the gate.
- Agent permission error and Tutorial Lesson 6 told users to pass --grant /
--grantAll to the btrace client, which does not parse them; point at the
agent-side grant= args and the permission policy instead. Usage text named
btrace-agent.jar/java-agent.jar instead of the masked btrace.jar.
- README/GettingStarted download URLs used asset names that never existed
(assets are btrace-v<version>-bin.tar.gz with bin/ and libs/ at the root).
- Drop the Oracle Contributor Agreement requirement left over from the GPL
era; contributions follow CONTRIBUTING.md under Apache-2.0.
- InstrumentationBackends.md still said the ClassFile API backend supports only
ENTRY/RETURN; all 17 probe kinds are supported now (only @duration on
SYNC_ENTRY/SYNC_EXIT is not).
- Tutorial 08 manifest snippet (Loader is the Premain/Agent class), tutorial 01
Ctrl+C menu semantics, JDKCompatibility EA id and pre-17 deprecation note,
releasing.md SDKMAN task and release-type guidance, broken FAQ/README anchors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
… release job
The GitHub release body was produced only by generate_release_notes, which for
a 393-commit major release yields a flat, uncategorised PR list. Add
.github/release-notes/v3.0.0.md (highlights, breaking changes, deprecations,
behavior notes, install, links) and a release step that uses it as body_path
when a curated file exists for the version, keeping the auto-generated list
appended after it.
Also: SECURITY.md supported-versions table; migration guide rows/sections for
the authenticated prepared endpoint (2.x clients rejected), the removed
@RequestPermission(s)/@RequiresPermission(s) annotations, and client-side
suppression of the Java deprecation notice; README pointer to the notes;
release/tutorial plan status refreshed to the audited state.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
…s 03, 08, 09
- 08: embedded privileged extensions are gated like filesystem ones
(EmbeddedExtensionRepository honors BTrace-Extension-Permissions), so Step 4
passes allowExtensions=btrace-metrics on the -javaagent line and shows the
real "! ERROR" / "btrace -le" output otherwise; Step 5 uses the jar the demo
build actually produces (demo-btrace-agent.jar) and launches DemoApp.java;
zip-name and task-list explanations corrected from the plugin source.
- 09: no official image contains a fat agent (btrace-dist writes it to
build/fat-agent and the Docker context depends only on btraceJar), so Step 6
builds it and copies it from the build context; the btrace-scripts ConfigMap
the pod mounts is now created (from demo/OrderTiming.java) before apply and
deleted in cleanup; stale docker/README remark removed; base-image tags
aligned with the Dockerfiles. Same Dockerfile fix in fat-agent-plugin.md.
- 03: inline listing and migrate-script output replaced with the real file and
real --dry-run output (9 matches incl. the header comment), repo-root paths,
and suppression advice for both the target and the client JVM.
- 04/07 expected output shows 3.0.0; README/Lesson 13 describe tutorial 05 as
the pointer to btraceio/agent-plugins it is; JAVA_HOME prerequisite stated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
… code
- B1: libs/profiles are removed in 3.0.0 (agent logs an error and loads
nothing), not deprecated; three runtime tiers (8 / 9-10 / 11+), not five;
adds the aggregation fixes, detach-safe indy dispatch, and the pre-release
permission annotations that were removed before 3.0.0.
- B2: jbang coordinate is io.btrace:btrace; Ctrl+C opens the client menu
(1 exit retracts the probe, 6 detach leaves it running); stale catalog
caveat dropped since the release workflow rewrites the catalog.
- B5: io.btrace:btrace coordinates; the Gradle plugin's service auto-detection
uses the io.btrace descriptors (the "dead code" gotcha was fixed in #897).
- B7 / v6 script: the MCP server lives in btraceio/agent-plugins
(btrace-observability, JBang launch); tool-level claims carry VERIFY
markers until checked against that repository.
- B8: fat-agent manifest uses io.btrace.boot.Loader + BTrace-Agent-Main; Maven
fat-agent paragraphs removed (module not shipped); embedded extensions honor
permissions; bundled probes fail loudly when missing.
- B9: the architecture doc already uses the real btrace.comm.* property names;
the JMH harness (BinaryProtocolBenchmark, :btrace-core:jmh) exists but no
results are committed.
- B10: all 17 probe kinds supported by the ClassFile API backend; ASM 9.10.1.
- All: fixed ../GettingStarted.md links, 3.0.0-SNAPSHOT sample output,
TODO markers for per-post Discussions threads and missing recordings.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
- @RequestPermission(s)/@RequiresPermission(s) were added on 2025-12-26 and
removed on 2026-01-07 (f476295); they are absent from the v2.2.6 tree, so
they are not a 2.x -> 3.0 breaking change. Reworded the release notes, the
migration guide, and tutorial 03 accordingly.
- The bundled ASM is 9.10.1 (settings.gradle) and defines V26/V27; the
MAX_ASM_MAJOR_VERSION = 69 ceiling is a routing decision to the ClassFile
API backend, not the parser's limit. Comments and InstrumentationBackends.md
said "ASM 9.9.x tops out at 69".
- BTraceTutorial Lesson 15 still described the ClassFile API backend as
ENTRY/RETURN-only.
- Tutorial 04's expected "Blocked privileged extension" line lists the three
permissions the built btrace-metrics manifest declares.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
JDK 27 went GA on 2026-09-15 (Oracle build 27+35). Verified locally on that
build: JDK warning gate passes with no warning, full integration suite green
(72 tests, 0 failures), ClassFile API smoke runs. Update the canonical support
sentence everywhere it appears, mark 27 as supported in JDKCompatibility.md
(the CI lane still uses the last EA build until SDKMAN publishes a GA id), and
record the JDK 27 and ASM 9.10.1 findings in the readiness review.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
…tests on JDK 28 EA
ASM 9.10.1 (already the newest release on Maven Central) parses class files
up to major 71 (Java 27), so MAX_ASM_MAJOR_VERSION moves from 69 to 71: Java
26 and 27 class files now take the ASM backend and the ClassFile API backend
covers Java 28+ (early-access builds). Tests follow: ClassFileApiBackendTest
fixtures are version 72 and require JDK 28, the classFileApiBackendTest task
uses a JDK 28 toolchain (continuous.yml installs 28-ea), BackendSelectorTest
and AsmInstrumentationBackendTest assert the new boundary, and the ClassFile
integration smoke assumes a JDK 28+ target.
CI matrix: add a 27-open GA lane; keep the 27.ea entry so the Monday JDK
update automation advances it to 28 EA (the script only rewrites an existing
EA entry).
release.yml: every Gradle job now installs JDK 24 (toolchain for the agent's
java24 source set, which continuous.yml had and release.yml did not) and runs
Gradle on JDK 21; three jobs ran Gradle 9 on JDK 11, which Gradle 9 does not
support. Drop the tool-cache step that skipped setup-java on a cache hit.
Docs: InstrumentationBackends.md, Tutorial Lesson 15, release notes, JDK
compatibility table (27 GA + 28 EA rows), releasing/checklist matrix lists,
draft B10; remove the last Oracle Contributor Agreement mention (FAQ).
Verified: spotless + core/agent/client unit tests green; integration suite
green on a JDK 27 GA target with the new ceiling.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
Docker Hub cannot host the documented btrace/btrace images: that namespace is
an unrelated user account (joined 2020, owns kafka-topic-operator), and an
organization namespace would need a paid Team plan. GitHub Container Registry
is free for public images and needs no account or secret, so:
- new release job publish-container-images (after create-github-release)
builds the Debian, Alpine, and distroless variants for linux/amd64 and
linux/arm64 from the candidate distribution, pushes <version>/latest tags
with GITHUB_TOKEN (packages: write), and verifies manifests and OCI labels;
summary table row added.
- every image reference (README, docker/README, GettingStarted, QuickReference,
tutorial 09 + demo Dockerfile/pod, fat-agent-plugin.md, release notes, draft
B8, Gradle buildDockerImage* tags) now uses ghcr.io/btraceio/btrace.
- docs/releasing.md documents the job and the one-time step of making the
package public.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
… parse them
PR #956 CI on a JDK 28 EA target failed
ClassFileApiBackendTest.callProbeBeforeAllowsAssignableTargetInstanceAndMethodName:
the ClassFile API backend instrumented the class, but the type-assignability
check (InstrumentUtils.isAssignable -> ClassInfo.loadExternalClass) still read
java/util/ArrayList with ASM, which throws "Unsupported class file major
version 72", so the hierarchy did not resolve and the CALL probe was skipped.
Class-header reads now dispatch the same way instrumentation does:
ClassHeaderReader uses ASM up to MAX_ASM_MAJOR_VERSION and the JDK ClassFile
API (ClassFileApiClassHeaderReader in the java24 source set, loaded
reflectively like ClassFileApiLinkerGuard) above it. On a JVM without the
ClassFile API a newer class file fails with an explicit message and ClassInfo
stays unavailable, never handed to ASM.
Tests: ClassHeaderReaderTest (ASM path, dispatch, fallback without the
ClassFile API) and two ClassFileApiBackendTest cases (forced ClassFile API
read on any JDK 24+, major-72 hierarchy resolution on JDK 28+).
CodeQL: `-x test build` still ran classFileApiBackendTest, which needs a JDK
28 toolchain that workflow does not install; exclude the task and drop the
now-unneeded JDK 26 setup.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
…Reader
CodeQL's default-setup analysis flagged the two locals as "never read": it
cannot resolve ClassHeader across the java24 source set, so the constructor
call that reads them looked unresolved. Pass the expressions directly; no
behavior change (re-verified on JDK 27 through the forced ClassFile API path).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
PR #956 CI showed both JDK 27 lanes failing in "Prepare JDK": SDKMAN reports
`27-open` as not available, and the pre-existing `27.ea.31-open` download now
fails because that early-access build was retired when JDK 27 went GA (so the
EA lane is red on develop as well). Pinning an identifier requires an SDKMAN
lookup that cannot be done from every environment and goes stale at each GA,
so the two moving lanes now carry a spec instead of a pin:
- `27` -> newest 27 GA build (Temurin, else java.net, else Oracle)
- `28-ea` -> newest `28.ea.N-open` early-access build
scripts/resolve-sdkman-java.sh turns a spec into a concrete identifier from
`sdk list java` (full identifiers pass through unchanged; unknown specs fail
loudly), and the Prepare JDK steps export TEST_JAVA_HOME from the resolved
identifier. The pinned lanes (8, 11, 17, 21, 25) are unchanged and remain
maintained by scripts/update-jdk-versions.sh, which ignores the two specs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
Every integration lane on the previous head failed with
"sdkman-init.sh: line 65: ZSH_VERSION: unbound variable": the resolver runs
under `set -euo pipefail` and sdkman-init.sh reads unset variables, so the
source aborted and the identifier list was empty even for pinned lanes.
Relax `-u` around the source and the `sdk list java` call. Reproduced and
verified against a stand-in sdkman-init.sh with the same hazard and SDKMAN's
table layout (exact ids pass through, `27` and `28-ea` resolve, unknown specs
still fail loudly).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
…setup-java
`sdk list java` prints a curated subset per vendor, so the resolver rejected
the pinned `11.0.31-tem` lane and found no 28 EA build. SDKMAN also has no
`28.ea.N-open` at all yet (the 2026-09-14 update-jdk-versions run kept
`27.ea.31-open`), so an EA lane cannot come from SDKMAN.
- resolve-sdkman-java.sh: full identifiers pass through unchanged (`sdk
install` is the check); `<major>` resolves against the complete SDKMAN API
list that update-jdk-versions.sh already uses, with `sdk list java` only as
a fallback; `<major>-ea` is rejected with a pointer to setup-java.
- continuous.yml, release.yml: `-ea` lanes are installed by actions/setup-java
(Temurin EA), the same step the build job uses for classFileApiBackendTest;
TEST_JAVA_HOME comes from its `path` output. In release.yml the step runs
before the JDK 24/21 steps so Gradle keeps JAVA_HOME=21.
- docs: JDKCompatibility.md 28 EA row; readiness plan §10 row.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
… at run time
Pinned identifiers (8.0.492-tem ... 25.0.3-tem) are only valid until SDKMAN
drops the superseded build: `sdk list java` already lists only 25.0.4-tem
for 25, and 27.ea.31-open was retired at 27 GA. Each pin is also stale
between a Temurin patch release and the Monday bump.
- continuous.yml, release.yml: matrix lanes are bare majors ('8' ... '27')
resolved by scripts/resolve-sdkman-java.sh (Temurin preferred); the
Gradle-side `sdk install java 11` line resolves the same way.
- v2-protocol-tests.yml: the `sdk` matrix column is gone; the JDK under test
resolves from the major.
- docs/JDKCompatibility.md: table describes the run-time resolution; the
Monday update-jdk-versions job has no pinned entries left to rewrite.
- readiness plan: pin-related rows updated.
Verified locally: the three workflows parse; the resolver maps 8/11/17/21/25/27
to the newest Temurin (or Oracle for 27) build of a canned API list;
update-jdk-versions.sh with a stubbed curl reports "up to date" and exits 1
without error.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
…he 27 lane
Run 35467559258 (lanes 8 and 11) showed that SDKMAN's `versions/all`
endpoint returns the same 40 curated identifiers as `sdk list java`: one
build per vendor and major, and on 2026-09-19 no Temurin 8, 11 or 21 build
at all, although the pinned 8.0.492-tem, 11.0.31-tem and 21.0.11-tem still
installed on the previous run. Its java.net entries (21.0.2-open,
25.0.2-open) are frozen GA builds. So SDKMAN cannot name the newest Temurin
build of an LTS major; actions/setup-java can (Adoptium API) and the build
job has used it for 24 and 28-ea all along.
- continuous.yml, release.yml: lanes 8/11/17/21/25 and 28-ea come from
actions/setup-java (temurin, check-latest); the 27 lane carries
`sdkman: true` and resolves through scripts/resolve-sdkman-java.sh
(27.0.0-oracle until Temurin 27 is published). The Gradle-side
`sdk install java 11` line is gone: toolchains come from the runner image
and the foojay resolver, as in the build job.
- v2-protocol-tests.yml: JDK under test from setup-java, handed to Gradle via
org.gradle.java.installations.paths.
- resolve-sdkman-java.sh: vendor order Temurin, Oracle, java.net.
- update-jdk-versions.sh: the build-JDK grep no longer aborts the script
under pipefail when the line is absent; the job stays in place.
- docs: JDKCompatibility.md, workflows README, readiness plan row.
Verified locally: the three workflows parse; resolver 27 -> 27.0.0-oracle
(27.0.1-tem once listed); the Monday script exits "up to date" on these
workflows and still proposes bumps on the pinned develop workflows.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
…ater quotes, stale ceiling text)
- release.yml: pin docker/setup-qemu-action (v3.7.0), docker/setup-buildx-action
(v3.12.0) and docker/login-action (v3.7.0) to commit SHAs from the official
repositories' tags, like every other third-party action in the workflow.
- v2-protocol-tests.yml: the JDK-under-test step ran after the JDK 21 step,
so setup-java left JAVA_HOME on the matrix JDK and Gradle 9 would start on
Java 11. The test JDK is installed first and JDK 21 last, unconditionally,
so JAVA_HOME always hosts the Gradle daemon.
- update-jdk-versions.sh: strip quotes around matrix entries so a quoted
pinned identifier is recognised again (verified with a stubbed API: quoted
pins are bumped, the current un-pinned matrix reports "up to date").
- InstrumentationBackend.java javadoc, docs/architecture/InstrumentationBackends.md:
ASM ceiling text was still 69; now 71 (Java 27), matching MAX_ASM_MAJOR_VERSION.
- .github/workflows/README.md: build job JDKs and the full test matrix.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
classFileApiReadAttempted is set to true before loadClassFileApiRead() publishes its result. If another class transformation reaches this method during that load, it takes the return null fast path and ClassInfo marks the hierarchy unavailable, so type-constrained/CALL probes can be dropped transiently on the first newer-class read. Publish the method before marking initialization complete (or make readers wait) so concurrent callers observe the same result.
…empted" flag; legacy version lookup in release.sh
Copilot's second pass found that ClassHeaderReader set the volatile
`classFileApiReadAttempted` flag before storing the loaded Method, so a
concurrent caller reading the flag outside the lock could see "attempted"
with a null reference and treat the ClassFile API as unavailable for one
transformation (ClassInfo then marks the hierarchy unavailable and drops
type-constrained/CALL probes). LinkerInstrumentor and BackendSelector carry
the same double-checked pattern with the same ordering; all three now store
the reference first and set the flag last, so a reader that observes the
flag observes the final reference (volatile happens-before).
BackendSelector.select javadoc still said "<= 69 (Java 25)"; it now refers
to MAX_ASM_MAJOR_VERSION (71, Java 27).
scripts/release.sh: the release-branch preview read `version = '...'` from
build.gradle only; release branches cut before c354f12 (2026-04-22, the
2.2.x line) carry `project.version = '...'` in common.gradle and showed an
empty version. The lookup now tries both files and both spellings
(verified: origin/release/2.2.6 -> 2.2.6, HEAD -> 3.0.0-SNAPSHOT).
Verified: :btrace-agent:spotlessCheck; ClassHeaderReaderTest (5) and
BackendSelectorTest (4) pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
…en parsing javaagent args
Main.loadArgs split the whole `-javaagent:btrace.jar=...` string on commas
and then read `key=value` pairs, so the documented `grant=NETWORK,THREADS`
(README, BTraceTutorial.md, messages.properties, the extension example
READMEs and the "permissions not granted" hint in Client) granted only
NETWORK: THREADS became a bare key. The same applied to deny=,
allowExtensions= and denyExtensions=.
parseAgentArgs (extracted from loadArgs, package-private for tests) now
re-attaches a bare token that directly follows one of those list-valued keys
to that key's list; `help` keeps working as a bare key and a bare token
after a scalar key still becomes a key, as before.
MainTest covers grant across a following scalar key, all four list keys,
the bare-token cases and null/empty input.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
…e on 2.2.x branches
The `patch` flow read `version = '...'` from build.gradle at the source ref
only; release branches cut before c354f12 (2026-04-22) keep
`project.version = '...'` in common.gradle, so `release.sh patch
release/2.2._` kept the checkout's version and would compute the wrong
release. A shared version_at_ref() now tries both files and both spellings
(verified: origin/release/2.2.6 -> 2.2.6, origin/release/2.2.4 -> 2.2.4,
HEAD -> 3.0.0-SNAPSHOT).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
This corrected section says embedded privileged extensions remain gated, but the canonical docs/BTraceTutorial.md:2037 still says the opposite: “embedding a privileged extension is the grant.” That conflicting guidance can cause users to deploy a fat agent without allowExtensions/allowPrivileged; update the remaining tutorial claim as part of this correction.
…agent args
loadBundledProbes splits `probes=` on commas and the docs write
`probes=DriverTracer,ExecutorTracer`, but the argument splitter turned the
second name into a bare key, so only the first bundled probe loaded. PROBES
joins the list-valued keys; MainTest covers it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
…hecked-out ref
All four version edits in release.yml rewrote `version = '...'` in
build.gradle only. 2.2.x release branches keep `project.version = '...'` in
common.gradle, so a patch release from such a branch would change nothing
and the following commit would fail with nothing to commit. Each step now
picks build.gradle when it carries a `version = '...'` line and
common.gradle otherwise, rewrites `(project.)?version = '...'`, and the
commit steps add that file (passed through GITHUB_ENV). Verified the snippet
against HEAD's build.gradle and origin/release/2.2.6's common.gradle.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
Lesson 14 said embedding a privileged extension "is the grant"; Tutorial 8
and the code say otherwise (the fat-agent plugin copies
BTrace-Extension-Permissions into the embedded descriptor,
EmbeddedExtensionRepository parses it, ExtensionBridgeImpl refuses the
extension unless allowExtensions/allowPrivileged is set). Lesson 14 now
matches.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this change do?
Final readiness pass for BTrace 3.0.0. The review itself is in
internal/plans/2026-09-19-v3.0.0-final-readiness.md(every claim carries a file:line or a command run with its result). The branch fixes what the review found:scripts/release.shand threerelease.ymlsteps read/wroteproject.versionincommon.gradle, a line removed inc354f12b; the version lives in the rootbuild.gradle.btrace --versionprinted(dev)on every Gradle 9 build becauseProject.execno longer exists (nowproviders.exec). Every Gradle job inrelease.ymlnow installs the JDK 24 toolchain the agent'sjava24source set needs and runs Gradle on JDK 21 (three jobs ran Gradle 9 on JDK 11, which it does not support). Both the script and the workflow still handle 2.2.x release branches, which keepproject.versionincommon.gradle.-Dbtrace.suppressJavaDeprecationWarning=trueproperty; both negative cases were confirmed to fail.MAX_ASM_MAJOR_VERSION69 → 71: the bundled ASM 9.10.1 (newest on Maven Central) parses Java 27 class files, so Java 26/27 use the ASM backend and the ClassFile API backend covers Java 28+.ClassFileApiBackendTestuses version-72 fixtures on a JDK 28 EA toolchain (continuous.ymlinstalls28-ea); selector/backend tests assert the new boundary.ClassInforead JDK classes with ASM and threwUnsupported class file major version 72, soisAssignablefailed and CALL probes were dropped.ClassHeaderReadernow dispatches like instrumentation does: ASM up to the ceiling, the JDK ClassFile API above it (ClassFileApiClassHeaderReaderin thejava24source set, loaded reflectively). The lazy lookups inClassHeaderReader,LinkerInstrumentorandBackendSelectorpublish their reference before the "attempted" flag, so a concurrent caller can no longer observe "attempted" with a null reference.-javaagent:btrace.jar=grant=NETWORK,THREADSgranted only NETWORK: the agent splits the whole argument string on commas before readingkey=valuepairs, so the second name became a bare key. The same applied todeny=,allowExtensions=,denyExtensions=andprobes=, all documented in the comma form. A bare token that directly follows one of those keys now continues that key's list (MainTestcovers it);helpstill works as a bare key.actions/setup-java(check-latest: true); the 27 lane is markedsdkman: trueand resolves throughscripts/resolve-sdkman-java.shto the newest 27 GA build SDKMAN lists (27.0.0-oracleuntil Temurin 27 is published). CI on this PR showed why: SDKMAN retired27.ea.31-openat 27 GA, rejected27-open, and its listing (the API andsdk list javaalike) carried no Temurin 8/11/21 build at all while the old pins still installed.update-jdk-versions.ymlstays and becomes active again as soon as a lane pins a build.ghcr.io/btraceio/btracefrom a newpublish-container-imagesrelease job (amd64+arm64, three variants,GITHUB_TOKEN, Docker actions pinned to commit SHAs); thebtraceDocker Hub namespace belongs to an unrelated account. All image references updated..github/release-notes/v3.0.0.md, wired into the release body viabody_path.--grantto the client (not parsed); download URLs used asset names that never existed; Oracle Contributor Agreement references removed (project is Apache-2.0, no CLA); tutorials 03/08/09 had steps that could not work as written; ClassFile-backend limitations were stale; Lesson 14 claimed embedding a privileged extension "is the grant" (it is not: the fat-agent plugin carries the permissions manifest into the embedded descriptor and the bridge still gates it); Java support statement is now "8–27+" (JDK 27 GA 2026-09-15); SECURITY.md supported-versions table; blog drafts fact-checked.Related issue
Release plan
internal/plans/2026-07-11-v3.0.0-release-plan.md; touches #932 (mitigations only, root cause stays open) and supersedes the pins in #938.Closes N/A
Scope and compatibility
Compatibility or migration notes:
Java 26 and 27 class files are now instrumented by the ASM backend instead of the ClassFile API backend (
docs/architecture/InstrumentationBackends.mdupdated). The agent's "permissions not granted" message now points at agent-sidegrant=/ policy instead of the nonexistent client--grantflag, and multi-valuegrant=/deny=/allowExtensions=/denyExtensions=/probes=lists on the-javaagentline now take effect in full. Container images move frombtrace/btrace(never published) toghcr.io/btraceio/btrace.Testing
spotlessCheckCommands and results:
Not verifiable in the authoring environment (no JDK 28 EA or Docker reachable):
classFileApiBackendTeston JDK 28 EA and the GHCR publish job (skipped on dry runs; first exercised by the real release). CI on the final code headdb2abfe5(run 35471638858) is green on every lane: Temurin 8.0.504+1, 11.0.32+101, 17.0.20+101, 21.0.12+101, 25.0.4+101, Oracle JDK 27+35 via SDKMAN27.0.0-oracle, and Temurin 28-ea; the ClassFile API backend tests ran on JDK 28 EA in thebuildjob. Copilot review on that head reports no findings; all eleven review threads are resolved.Documentation and release impact
Release notes / follow-up work:
./scripts/release.sh minor(develop is already3.0.0-SNAPSHOT;majorwould produce 4.0.0). Run onedry_run: trueworkflow first; the Release workflow has never run.btraceGHCR package public.sdkman: trueentry for the 27 lane onceactions/setup-javacan serve Temurin 27.btraceio/agent-plugins.Final checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_012qpfQbkp5ASygBF8WncoL1
This change is