Skip to content

feat: use Java @-files for classpath to avoid avaje-jsonb 50K limit - #91

Merged
sake92 merged 3 commits into
mainfrom
argfile-chunking
Jun 16, 2026
Merged

feat: use Java @-files for classpath to avoid avaje-jsonb 50K limit#91
sake92 merged 3 commits into
mainfrom
argfile-chunking

Conversation

@sake92

@sake92 sake92 commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Offload classpath and JVM options from RunSubprocess JSON messages into Java @-files (argument files), dramatically reducing message size. Java's launcher natively expands @filepath on the command line.

  • Add Argfile utility to write @-files with jvmOptions + classpath
  • Update all RunSubprocess sites: run, runMain, runMvnApp, repl, fix, fixCheck, tools (8 sites total)
  • Extend chunking from Output-only to Output+Log messages in CliClientSocketWriter (renamed MaxOutputChunkSize -> MaxMessageChunkSize)
  • Zero client changes needed

Naming scheme: {key}-jvm-opts.txt (e.g. run-jvm-opts.txt, scalafix-jvm-opts.txt, -jvm-opts.txt)

Summary by CodeRabbit

  • Improvements
    • Enhanced Java process execution to use argument files for more reliable command construction and better handling of complex classpath configurations.
    • Improved message handling by implementing chunked JSON writing for large output and log messages, reducing serialization overhead.

Offload classpath and JVM options from RunSubprocess JSON messages
into Java @-files (argument files), dramatically reducing message size.
Java's launcher natively expands @filepath on the command line.

- Add Argfile utility to write @-files with jvmOptions + classpath
- Update all RunSubprocess sites: run, runMain, runMvnApp, repl,
  fix, fixCheck, tools (8 sites total)
- Extend chunking from Output-only to Output+Log messages in
  CliClientSocketWriter (renamed MaxOutputChunkSize -> MaxMessageChunkSize)
- Zero client changes needed

Naming scheme: {key}-jvm-opts.txt (e.g. run-jvm-opts.txt,
scalafix-jvm-opts.txt, <toolName>-jvm-opts.txt)
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sake92, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 32 minutes and 52 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0cf753cc-e8c5-4652-8717-0466600b3c19

📥 Commits

Reviewing files that changed from the base of the PR and between 73aa8f4 and 01c4b57.

📒 Files selected for processing (5)
  • server/src/ba/sake/deder/Argfile.scala
  • server/src/ba/sake/deder/CoreTasks.scala
  • server/src/ba/sake/deder/RunTasks.scala
  • server/src/ba/sake/deder/cli/CliClientMessageHandler.scala
  • server/test/src/ba/sake/deder/ArgfileSuite.scala
📝 Walkthrough

Walkthrough

Introduces a new Argfile utility that writes Java @argfile files containing JVM options and a classpath entry. All subprocess command construction across RunTasks, CoreTasks, and CliClientMessageHandler is migrated from inline -cp arguments to java @argfile`` invocations. Separately, CliClientSocketWriter gains a `writeChunks` helper that also covers `Log` messages in addition to `Output` messages.

Changes

Argfile utility and subprocess migration

Layer / File(s) Summary
Argfile utility and tests
server/src/ba/sake/deder/Argfile.scala, server/test/src/ba/sake/deder/ArgfileSuite.scala
New Argfile object exposes write(dir, key, jvmOptions, classpath) that validates the key, creates the directory, and writes a $key-jvm-opts.txt file with newline-separated JVM options plus -cp and classpath. Tests cover non-empty options, empty options, invalid key rejection, and missing parent directory creation.
Subprocess command migration to @argfile
server/src/ba/sake/deder/RunTasks.scala, server/src/ba/sake/deder/CoreTasks.scala, server/src/ba/sake/deder/cli/CliClientMessageHandler.scala
run, runMain, runMvnApp, repl, fixTask, fixCheckTask, and handleTool all replace their inline java -cp <classpath> command construction with Argfile.write followed by java @<argfile>.

CliClientSocketWriter chunking refactor

Layer / File(s) Summary
writeChunks helper and Log message chunking
server/src/ba/sake/deder/cli/CliClientSocketWriter.scala
Adds MaxMessageChunkSize constant and a writeChunks private method that splits large strings into fixed-size chunks and serializes each as newline-delimited JSON. Updates the run() loop to use writeChunks for both oversized Output and oversized Log messages.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 No more classpath walls too wide,
An argfile tucks the options inside.
-cp banished from the command line,
Chunked logs now split so clean and fine.
Hop hop hooray, the JVM runs with ease!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: using Java @-files (argument files) to work around the avaje-jsonb 50K size limitation by offloading classpath data.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch argfile-chunking

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/src/ba/sake/deder/Argfile.scala`:
- Around line 35-36: The jvmOptions and classpath variables are being written
unescaped to the `@argfile`, which causes Java's argument file parser to treat
spaces and special characters as delimiters, breaking arguments that contain
whitespace. Modify the code where lines is constructed to properly escape each
argument in jvmOptions and classpath before concatenating them. Specifically,
before calling mkString on the lines sequence, apply appropriate escaping or
quoting to each element to ensure spaces and special characters are preserved
when the file is parsed by Java.

In `@server/src/ba/sake/deder/cli/CliClientSocketWriter.scala`:
- Around line 28-31: The text chunking at the grouped(MaxMessageChunkSize) call
and the text length guards do not account for JSON escaping overhead. Characters
like quotes and backslashes that require escaping in JSON can cause the final
serialized JSON output to exceed the intended size limit even though the raw
text passed the length check. Fix this by validating the actual JSON-serialized
size after calling makeMsg(chunk).toJson() instead of relying on raw text.length
checks. This applies both to the chunking logic in the foreach block where text
is grouped and to any guard conditions that validate text.length before
serialization (around lines 41-43).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ffd7664-3f62-47a9-a906-760cc543b2fb

📥 Commits

Reviewing files that changed from the base of the PR and between f394355 and 73aa8f4.

📒 Files selected for processing (6)
  • server/src/ba/sake/deder/Argfile.scala
  • server/src/ba/sake/deder/CoreTasks.scala
  • server/src/ba/sake/deder/RunTasks.scala
  • server/src/ba/sake/deder/cli/CliClientMessageHandler.scala
  • server/src/ba/sake/deder/cli/CliClientSocketWriter.scala
  • server/test/src/ba/sake/deder/ArgfileSuite.scala

Comment thread server/src/ba/sake/deder/Argfile.scala Outdated
Comment on lines +28 to +31
text.grouped(MaxMessageChunkSize).foreach { chunk =>
val json = makeMsg(chunk).toJson(spaces = 0, sort = false)
outputStream.write((json + '\n').getBytes(StandardCharsets.UTF_8))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Chunking by raw text length can still exceed the JSON size limit after escaping.

At Line 41 and Line 43, the guard uses text.length, and at Line 28 chunking is also grouped(MaxMessageChunkSize). This does not account for JSON escaping overhead, so chunks with many escaped characters can still serialize past the intended limit (Line 29), defeating the avaje-jsonb protection goal.

Suggested fix
-  private val MaxMessageChunkSize = 30_000
+  private val MaxMessageChunkSize = 30_000

@@
   private def writeChunks(
       text: String,
       makeMsg: String => CliServerMessage,
       outputStream: java.io.OutputStream
   ): Unit =
-    text.grouped(MaxMessageChunkSize).foreach { chunk =>
-      val json = makeMsg(chunk).toJson(spaces = 0, sort = false)
-      outputStream.write((json + '\n').getBytes(StandardCharsets.UTF_8))
-    }
+    var i = 0
+    while i < text.length do
+      var lo = 1
+      var hi = math.min(MaxMessageChunkSize, text.length - i)
+      var best = 1
+      while lo <= hi do
+        val mid = (lo + hi) >>> 1
+        val candidate = text.substring(i, i + mid)
+        val json = makeMsg(candidate).toJson(spaces = 0, sort = false)
+        if json.length <= MaxMessageChunkSize then
+          best = mid
+          lo = mid + 1
+        else hi = mid - 1
+      val chunk = text.substring(i, i + best)
+      val json = makeMsg(chunk).toJson(spaces = 0, sort = false)
+      outputStream.write((json + '\n').getBytes(StandardCharsets.UTF_8))
+      i += best

@@
-          case CliServerMessage.Output(text) if text.length > MaxMessageChunkSize =>
+          case CliServerMessage.Output(text)
+              if CliServerMessage.Output(text).toJson(spaces = 0, sort = false).length > MaxMessageChunkSize =>
             writeChunks(text, CliServerMessage.Output(_), outputStream)
-          case CliServerMessage.Log(text, level) if text.length > MaxMessageChunkSize =>
+          case CliServerMessage.Log(text, level)
+              if CliServerMessage.Log(text, level).toJson(spaces = 0, sort = false).length > MaxMessageChunkSize =>
             writeChunks(text, CliServerMessage.Log(_, level), outputStream)

Also applies to: 41-44

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/ba/sake/deder/cli/CliClientSocketWriter.scala` around lines 28 -
31, The text chunking at the grouped(MaxMessageChunkSize) call and the text
length guards do not account for JSON escaping overhead. Characters like quotes
and backslashes that require escaping in JSON can cause the final serialized
JSON output to exceed the intended size limit even though the raw text passed
the length check. Fix this by validating the actual JSON-serialized size after
calling makeMsg(chunk).toJson() instead of relying on raw text.length checks.
This applies both to the chunking logic in the foreach block where text is
grouped and to any guard conditions that validate text.length before
serialization (around lines 41-43).

@sake92
sake92 merged commit d3c2960 into main Jun 16, 2026
4 checks passed
@sake92
sake92 deleted the argfile-chunking branch June 16, 2026 11:54
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