fix: address CodeRabbit findings from the release PR review - #798
Merged
Conversation
Verified each finding against the current code before applying (one, the Suricata kill-switch weighting in AnalysisService, was a false positive — the code's own comment documents that tradeoff as intentional and cosmetic; left unchanged). Fixing the rest: - SuricataEngine: clear the warm-engine flag when the control-socket check loses contact, not just when the daemon process itself exits, so a hung daemon doesn't keep reporting warm to later captures. - FileMapper: the size-based ETA fallback was missing the one-time cold-engine ruleset-build cost the packet-count path already accounts for, under-estimating ETA when capinfos fails or packet count is otherwise unknown. - AnalysisEtaTest: tightened a floor assertion from >=2s to >=10s to actually match FileMapper.MIN_ESTIMATE_SECONDS. - docs/README: corrected the upload-cap figure (16% / 327MB at the default budget, not the stale 25% / 512MB) in every place it was quoted — same mismatch flagged independently during the report-fix PR. - check_env_passthrough.py: documented_vars() now actually uses its path argument (resolved from the repo root) instead of silently ignoring it; the undocumented-var check now covers the union of all compose stacks instead of only "prod". Deferred as a follow-up rather than rushed here: PcapParserService's unbounded string pool (real edge case on high-cardinality captures, but needs a proper capacity choice backed by a benchmark and a dedicated test, not a same-release patch). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDyaaPSRNzNXRzJoS794C4
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
NotYuSheng
added a commit
that referenced
this pull request
Aug 20, 2026
…xes, and accumulated dev fixes (#795) * chore: sync app version to v0.1.1-338-g2ded9ff (#774) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * docs: record why dev is protected (it was deleted by its own release) (#775) The repository has "Automatically delete head branches" enabled, which is what stops merged feature branches piling up. A release PR's head branch is dev — so merging the first dev -> main release deleted dev. Nothing was lost: main and dev are identical at exactly that moment, so recreating dev from main restored it exactly. But any open PR based on dev would have been orphaned, and on a normal day that is most of them. dev now carries allow_deletions: false, matching main. GitHub will not delete a protected branch, so the repo setting keeps tidying feature branches and cannot touch either long-lived one. The delete-branch-on-close workflow already treats a 422 as "protected, leave it", so the protection is the exclusion list and no code changes. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * chore: sync app version to v0.1.1-340-g0a30809 (#776) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: weight the progress bar from measurement, not estimate (#758) (#777) The bar reached 23% almost immediately, sat there for 44 of a 47-second analysis, then swept to 100% in under two. A bar motionless for most of a job teaches people to ignore it, and an operator who cannot tell "working" from "hung" kills a job that was fine. The weights were a plausible guess: "Detecting applications & threats" at 35% of the job when it was 95%, DB writes at 20% when they were 0.6%. Two profiles now, because one vector cannot describe both. Suricata's detection engine costs ~45s to build and ~0.3s to use (#759), so the first capture after a restart has a completely different shape from every capture after it. SuricataEngine.isWarm() says which we are in. Measured shares from real runs rather than estimates: cold: detection 90% of the plan (measured 94.7%) warm: parse 21, detect 21, classify 30, extract-files 22 (measured 21.0 / 21.0 / 29.6 / 22.2) Verified live against the running stack. Cold now reads 5% and "Building threat-detection ruleset (first run)" rather than 23% and silence; warm climbs 3 -> 24 -> 44 -> 78 in step with the work. The cold stage is still motionless for ~45s, because the bar cannot advance inside a stage. Naming the wait as one-time setup is what separates "hung" from "working on something known to be slow", and is the honest fix short of reporting sub-stage progress. Still approximate at the extremes: parsing and DB writes scale with the capture while the rest does not, so these shares are right for a mid-sized capture. Re-weighting from the packet count after parsing is the next step and is deliberately not attempted here. Verified by mutation: collapsing back to one vector fails 2, zeroing a stage's weight fails 1. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * chore: sync app version to v0.1.1-342-g399628a (#778) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: make the upload cap fit the heap that has to parse it (#779) (#780) Stage 2 holds the whole capture in heap — every conversation, every packet, payloads included — and releases nothing until the database insert in stage 6. So the largest file we can accept is bounded by the JVM heap. Those two numbers were derived separately and nothing connected them. #92 set the upload cap at 25% of the memory budget when the heap was 75%: a capture could be at most a third of the heap. #586 lowered the heap to 50% to make room for native subprocesses and did not revisit the cap, so the margin fell from 3x to 2x. A 468MB capture was then accepted and died of OutOfMemoryError 25 minutes into parsing, taking the HTTP poller thread with it — the container stayed "running" while serving nothing. Two definitions of one limit, drifting, with nothing checking their relationship. The same shape as everything in #733. upload cap 25% -> 16% of the budget (327MB at the 2048MB default) FileServiceImpl stops hardcoding 500MB and reads app.max-file-size, so the number /system/limits shows the user and the number actually enforced are the same number scripts/check_memory_budget.py asserts the relationship and fails the build on any of the three ways it broke: widening the cap, shrinking the heap, or re-hardcoding the limit. Verified by planting each. This does not raise capacity. A 468MB capture is still beyond this deployment — it is now refused in seconds with 413 rather than accepted and failed after 25 minutes of work. Raising APP_MEMORY_MB raises both numbers together; making capture size independent of heap needs stage 2 to stream, which stays open on #779. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * chore: sync app version to v0.1.1-344-g0395fe3 (#781) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: give the analysis ETA a fixed-cost term (#758 follow-on) (#782) The estimate was seconds-per-packet with no constant. That reads 91 minutes for a job of roughly 36, and 10 seconds for one that takes 49 — the same missing term, in opposite directions, which is why it looked like two unrelated bugs. Measured cost per 1000 packets falls ~8x between a 250-packet capture and a 45,000-packet one. That is a fixed cost being amortised as if it scaled. seconds = fixed + per_kpkt * (packets / 1000) Fitted by scripts/calibrate_analysis_eta.py over runs of 2k-45k packets, which also learned to exclude cold-engine runs: one of those dragged the detection term's fixed cost to 22s and flattened its slope to zero, because a ruleset build is a different regime, not an outlier. predicted 4s / 9s / 20s / 43s measured 3.4s / 8.5s / 17.3s / 44.0s Suricata's ~45s engine build is a separate one-time term, so a capture that pays it is estimated as the different job it is — the same cold/warm split the progress bar makes. The port for that lives in common.stage rather than analysis.spi: analysis already depends on file, so putting it there would close the analysis <-> file cycle #512 slice 1 was written to break. ArchUnit caught that; both modules depend on the port instead, as with common.net.LocalityPolicy. The old size-based path stays as a fallback for files with no packet count, deliberately cruder — bytes are a poor proxy when a packet-dense capture costs far more than its size suggests. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * chore: sync app version to v0.1.1-346-ga91ca51 (#783) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * perf: share the strings every packet repeats (#779) (#784) A capture has a few hundred distinct addresses and a handful of protocols, but millions of packets, and split() returns a fresh String for every field of every packet. At ~776 bytes per PacketInfo, 1.7M packets need ~1.26 GB against a 1 GB heap — which is exactly the OutOfMemoryError that made a 468MB capture unanalysable. Addresses and protocols are ~150 of those bytes and are almost entirely duplicates. They now come from a per-parse pool. A local map rather than String.intern(): intern's table is JVM-wide and permanent, so a capture's addresses would outlive the analysis that read them. This is discarded with the parse. HONEST LIMITS. This raises the ceiling, it does not remove it: the per-packet object count is unchanged and memory still scales with the capture. I have not measured the end-to-end saving — doing so needs an A/B on the same file with and without, and the shared stack was in use. The unit tests verify the objects are genuinely shared (by identity, not equality, since equal-but-distinct is precisely the waste); the size of the win on a real capture is unmeasured. The structural fix is streaming stage 2 to the database instead of accumulating, which is written up on #779 and stays open. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * chore: sync app version to v0.1.1-348-g183d380 (#785) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * ci: run the tool-dependent tests for real, and notice when they cannot (#701) (#786) The 4-point coverage gap between CI and a developer's machine is not a measurement artifact. Total instructions are identical in both (64,663), so it is not Lombok filtering, not excludes, not a stale target/ — the same classes are analysed and 2,135 more simply execute locally. They are all classes that shell out to a tool: PcapParserService (+739), AnalysisService (+350), WebServerLogExtractor (+282), DnsQueryLogExtractor (+255), HostnameResolverService (+248). This host has tshark; ubuntu-latest does not. Those services degrade gracefully by design, so a missing binary is not an error: the subprocess fails to start, the catch fires, the method returns early, and every assertion still holds. DnsQueryLogExtractorTest reports 13 passed with tshark working and 13 passed with it replaced by a shim that exits 127 — it would pass if the parsing were deleted. So the tests were not skipped in CI, they were hollow, which is why the counts matched exactly and only coverage disagreed. CI now installs tshark, and ExternalToolsAvailableTest asserts the precondition rather than leaving a future absence silent. Suricata is deliberately not installed: ~700MB of ruleset and ~45s of engine build per run (#569), already covered by the full-stack job. A note on method: my first attempt to demonstrate this stripped PATH, which proved nothing — /bin is a symlink to /usr/bin, so tshark stayed reachable and both runs were identical for the wrong reason. The shim is what actually shadows it. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * chore: sync app version to v0.1.1-350-gd5c4583 (#787) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * docs: document every env var, and gate the third edge (#779 follow-on) (#788) .env.example still promised "Max upload size = 25% of the effective budget" and "2048 -> 512 MB max upload". #780 changed that to 16% today, so the file was telling operators they could upload 512MB while the app enforced 327MB — the same advertised-versus-enforced split that made a 468MB capture fail 25 minutes in. Corrected, with the reason recorded rather than just the number. Eleven variables compose passes were undocumented, including every one I added today: the warm Suricata engine's five, LLM_TOOL_CALLING, the cluster tuning knobs and the DNS/HTTP suspicion thresholds. A setting nobody knows about is not configurable. check_env_passthrough.py already checked compose against Spring config in both directions. It now checks the third edge — a variable can be wired end to end and still be invisible, because .env.example is the only place an operator looks. Verified by planting a new compose variable and watching it fail. Exempt: DATABASE_*, MINIO_* and TZ, which compose derives from variables that ARE documented (DATABASE_URL from POSTGRES_DB, MINIO_ACCESS_KEY from MINIO_ROOT_USER). Documenting the derived name would invite someone to set it and wonder why the source still won. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * chore: sync app version to v0.1.1-352-gf89e142 (#789) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: raise Jackson's 20MB JSON string cap for report topology diagrams (#793) * fix: raise Jackson's 20MB JSON string cap for report topology diagrams (#792) Report generation embeds the topology diagram as a base64-encoded PNG in the JSON request body; a dense enough network topology pushes that string past Jackson's default 20MB max-string-length, rejecting the request before it reaches the controller. Cap now scales with APP_MEMORY_MB (2.5% of the effective budget, clamped to 8-256MB) like the existing upload-size/timeout derivation in docker-entrypoint.sh, instead of a flat default that's disproportionate on small/large deployments. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDyaaPSRNzNXRzJoS794C4 * fix: sync Jackson max-string-length fallback with the entrypoint's derived default Code review on #793 caught that application.yml's fallback (52428800 = 50MB, used only when docker-entrypoint.sh isn't in the picture — mvn spring-boot:run, IDE runs, tests) didn't match what the entrypoint actually derives at the documented default (APP_MEMORY_MB=2048 -> 51MB = 53477376 bytes), so a bypass-path run silently behaved differently from the real deployment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDyaaPSRNzNXRzJoS794C4 * ci: exempt JACKSON_MAX_STRING_LENGTH from the compose passthrough check Same as MAX_UPLOAD_SIZE_BYTES/ANALYSIS_TIMEOUT_SECONDS: it's passed as a JVM -D system property by backend/docker-entrypoint.sh, derived from the memory budget, not set in compose's services.backend.environment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDyaaPSRNzNXRzJoS794C4 --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * chore: sync app version to v0.1.1-354-g153560c (#794) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * chore: sync app version to v0.1.1-358-g074a635 (#796) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: address CodeRabbit findings from the release PR review (#798) Verified each finding against the current code before applying (one, the Suricata kill-switch weighting in AnalysisService, was a false positive — the code's own comment documents that tradeoff as intentional and cosmetic; left unchanged). Fixing the rest: - SuricataEngine: clear the warm-engine flag when the control-socket check loses contact, not just when the daemon process itself exits, so a hung daemon doesn't keep reporting warm to later captures. - FileMapper: the size-based ETA fallback was missing the one-time cold-engine ruleset-build cost the packet-count path already accounts for, under-estimating ETA when capinfos fails or packet count is otherwise unknown. - AnalysisEtaTest: tightened a floor assertion from >=2s to >=10s to actually match FileMapper.MIN_ESTIMATE_SECONDS. - docs/README: corrected the upload-cap figure (16% / 327MB at the default budget, not the stale 25% / 512MB) in every place it was quoted — same mismatch flagged independently during the report-fix PR. - check_env_passthrough.py: documented_vars() now actually uses its path argument (resolved from the repo root) instead of silently ignoring it; the undocumented-var check now covers the union of all compose stacks instead of only "prod". Deferred as a follow-up rather than rushed here: PcapParserService's unbounded string pool (real edge case on high-cardinality captures, but needs a proper capacity choice backed by a benchmark and a dedicated test, not a same-release patch). Claude-Session: https://claude.ai/code/session_01QDyaaPSRNzNXRzJoS794C4 Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * chore: sync app version to v0.1.1-360-g1fe6984 (#799) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
CodeRabbit reviewed PR #795 (the dev -> main release PR) and left 8 comments. Verified each against the current code before acting — not all of them held up:
AnalysisService.java— CodeRabbit wanted the Suricata progress-bar weighting to honor the global kill-switch, but the code's own comment already documents that as an intentional tradeoff ("the per-file flag, not the effective state... being wrong costs a slightly misshapen bar" — cosmetic only).PcapParserService.java's unbounded per-parse string pool. Genuine edge case (high-cardinality captures could make memory usage worse, not better), but the right fix needs a benchmarked capacity choice and a dedicated test, not a same-release patch. Filed as PcapParserService's per-parse string pool has no capacity bound #797.SuricataEngine.java: clear the warm flag when the control-socket check loses contact, not just when the process exits — a hung-but-alive daemon no longer keeps reporting warm to later captures.FileMapper.java: the size-based ETA fallback was missing the cold-engine ruleset-build cost the packet-count path already includes.AnalysisEtaTest.java: tightened>= 2sto>= 10sto actually matchMIN_ESTIMATE_SECONDS.docs/configuration/environment-variables.rst+README.md: corrected the stale 25%/512MB upload-cap figure to the real 16%/327MB (default budget) everywhere it was quoted — independently caught by CodeRabbit; matches a doc/code mismatch already flagged during fix: raise Jackson's 20MB JSON string cap for report topology diagrams #793's work.check_env_passthrough.py):documented_vars()was ignoring its ownpathargument (Ruff flagged the unused arg too); the undocumented-var check only validated theprodcompose stack, missingoffline/offline-prod.Test plan
mvn test -Dtest=SuricataEngineTest,AnalysisEtaTest— 14/14 passing.python3 scripts/check_env_passthrough.py— still passes, now actually exercising the fixed code paths.python3 -m py_compile scripts/check_env_passthrough.py— syntax OK.🤖 Generated with Claude Code
https://claude.ai/code/session_01QDyaaPSRNzNXRzJoS794C4