Skip to content

ci: gate the packaged jar on the Java 8 class-file floor - #154

Merged
bernardladenthin merged 1 commit into
mainfrom
claude/bytecode-version-gate
Sep 3, 2026
Merged

ci: gate the packaged jar on the Java 8 class-file floor#154
bernardladenthin merged 1 commit into
mainfrom
claude/bytecode-version-gate

Conversation

@bernardladenthin

Copy link
Copy Markdown
Owner

Summary

  • maven.compiler.release governs only the code we compile. A dependency built for a newer Java lands in the jar untouched and nothing in a normal build objects; the failure surfaces at a consumer's JVM as UnsupportedClassVersionError, which is the worst possible place to find it. It has already happened twice in the sibling repos — checker-qual 4.x (@Retention(RUNTIME), so any reflection over an annotated element loads it) and logback-classic 1.4.0+, whose LogbackServiceProvider SLF4J's ServiceLoader loads at startup.
  • Adds the cross-repo shared .github/verify-bytecode-version.sh and runs it in the smoke-jar job. Byte-identical in java-llama.cpp / BitcoinAddressFinder / streambuffer / srcmorph; the checksum lives in workspace/crossrepostatus.md. --max-major is passed from the workflow so the ceiling sits next to the release it belongs to (52 here) rather than being duplicated inside the script per repo.
  • This repo is the least exposed of the four — no logger, and the deliverable is the plain library jar rather than a fat jar — so the step is a regression guard rather than a fix. It runs before the smoke test so an unloadable class is reported as such instead of as a mysterious smoke failure.

Script shape worth knowing:

  • module-info.class and META-INF/versions/** are skipped unconditionally. A classpath JVM never loads either, so a high version there is not a defect (and it is what keeps this repo's release 9 module-info out of scope); waiving them per-repo would only invite blanket exceptions.
  • Exit 2 on an empty scan. A run that found no jars must never read as a pass.
  • Paths may be jars or directories, so one invocation covers the whole downloaded artifact.

Test plan

  • Verified locally against a real mvn package: 5 classes across 2 jars, 0 over major 52
  • Workflow YAML parses
  • CI is green on this branch
  • Docs updated — new Java 8 bytecode floor section in CLAUDE.md

No source, test or dependency change; this PR is a CI step plus the shared script and its documentation.

Related issues / PRs

Companion PRs add the same gate to java-llama.cpp, BitcoinAddressFinder and srcmorph, and record it in the workspace checksum table.

Checklist

  • I have read CONTRIBUTING.md and CODE_OF_CONDUCT.md
  • My commits follow Conventional Commits
  • No security-sensitive changes

🤖 Generated with Claude Code

https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH


Generated by Claude Code

`maven.compiler.release` governs only the code we compile. A dependency built
for a newer Java lands in the jar untouched and nothing in a normal build
objects; the failure surfaces at a consumer's JVM as
UnsupportedClassVersionError, which is the worst possible place to find it.
It has already happened twice in the sibling repos (checker-qual 4.x, and
logback-classic 1.4.0+ whose LogbackServiceProvider SLF4J's ServiceLoader
loads at startup).

Adds the cross-repo shared `.github/verify-bytecode-version.sh` and runs it in
the `smoke-jar` job. The script is kept byte-identical in java-llama.cpp,
BitcoinAddressFinder, streambuffer and srcmorph; the checksum lives in
workspace/crossrepostatus.md. `--max-major` is passed from the workflow so the
ceiling sits next to the release it belongs to (52 here) rather than being
duplicated inside the script per repo.

This repo is the least exposed of the four — no logger, and the deliverable is
the plain library jar rather than a fat jar — so the step is a regression guard
rather than a fix. It runs before the smoke test so an unloadable class is
reported as such instead of as a mysterious smoke failure.

Notes on the script's shape:
  * `module-info.class` and `META-INF/versions/**` are skipped unconditionally.
    A classpath JVM never loads either, so a high version there is not a defect
    and waiving them per-repo would only invite blanket exceptions.
  * exit 2 on an empty scan. A run that found no jars must never read as a pass.
  * paths may be jars or directories, so one invocation covers a whole artifact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
@claude

claude Bot commented Sep 3, 2026

Copy link
Copy Markdown

Review Summary

This PR adds excellent infrastructure for preventing Java version incompatibilities in packaged artifacts. The implementation is solid, well-documented, and security-conscious.

Strengths

Clear motivation: The PR documentation explains the real-world failure modes (checker-qual, logback) that this guards against, making the maintenance burden transparent for future reviewers.

Robust error handling:

  • Exit code 2 for empty scans prevents silent failures
  • Graceful handling of unreadable/malformed jars
  • Proper argument validation with clear usage messages
  • Validates paths exist before processing

Smart design choices:

  • Python for efficient zip reading (avoids spawning processes per class)
  • Reports one example per jar rather than overwhelming output
  • Skips module-info.class and META-INF/versions/ unconditionally (correct for classpath mode)
  • Supports --allow patterns for documented exceptions

Security: No shell injection risks (proper quoting), no path traversal issues, minimal dependencies (python3).

Documentation: Excellent inline comments, comprehensive README in script header, well-integrated CLAUDE.md section.

Cross-repo consistency: The byte-identical synchronization requirement is clearly documented with checksum tracking.

Minor Observations

  • The script correctly parses variable-length allow patterns by passing the count as argv[2], then slicing — clean approach.
  • Java version calculation (major - 44) is mathematically correct across Java 8-21, though passing an invalid --max-major would show nonsensical output (not a real concern since it's workflow-controlled).

No bugs or security issues found. The placement in the workflow (before smoke test) is correct.

@sonarqubecloud

sonarqubecloud Bot commented Sep 3, 2026

Copy link
Copy Markdown

@bernardladenthin
bernardladenthin merged commit 86a4ae0 into main Sep 3, 2026
17 of 20 checks passed
@bernardladenthin
bernardladenthin deleted the claude/bytecode-version-gate branch September 3, 2026 22:05
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.

2 participants