Skip to content

relayburn-analyze: port tool-output-bloat detector#286

Merged
willwashburn merged 2 commits into
mainfrom
claude/close-issue-271-webhooks-Di7rr
May 5, 2026
Merged

relayburn-analyze: port tool-output-bloat detector#286
willwashburn merged 2 commits into
mainfrom
claude/close-issue-271-webhooks-Di7rr

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary

Ports packages/analyze/src/tool-output-bloat.ts to the relayburn-analyze Rust crate. Brings up both signal sources from the TS implementation:

  • Signal A (static config): reads BASH_MAX_OUTPUT_LENGTH from ~/.claude/settings.json and <cwd>/.claude/settings.json, converts the char-unit value to tokens via the same bytes/4 heuristic, and flags when the merged value exceeds the threshold (default 15k tokens). Project settings override user settings.
  • Signal B (observed bloat): aggregates tool_result / function_call_output events whose enriched approxTokens (or bytes/4 fallback for legacy ledgers) exceed the threshold, bucketed by (source, normalizedToolName), priced via the source turn's model rate.
  • WasteFinding adapter: emits a settings.json paste suggestion for Signal A (at the 60000-char boundary that matches the 15k-token threshold) and a CLAUDE.md / AGENTS.md instruction paste for Signal B.

Public surface mirrors @relayburn/analyze: BASH_MAX_OUTPUT_ENV_KEY, DEFAULT_BLOAT_TOKEN_THRESHOLD, detect_observed_bloat, detect_static_config_bloat, detect_tool_output_bloat, load_claude_settings, project_claude_settings_path, user_claude_settings_path, tool_output_bloat_to_finding, plus all the typed records and request shapes.

Foundation crate is sync, so the loader uses std::fs::read_to_string and returns Option<LoadedClaudeSettings>None means missing or malformed JSON (indistinguishable from "no waste"), matching the TS undefined behavior.

Test plan

  • cargo test -p relayburn-analyze — 114 tests pass (38 added for the new module).
  • cargo test --workspace — full Rust suite green.
  • cargo build --workspace --all-targets — matches CI's build step.
  • Conformance: ports every case from tool-output-bloat.test.ts (Signal A merge + threshold semantics, settings.json fixture, observed bloat aggregation across sources, custom thresholds, orphan-event fallback, carrier vs. notification de-dup, finding adapter shape).
  • Fixture-driven cases reuse the shared tests/fixtures/claude/oversized-bash-output.jsonl, tests/fixtures/codex/oversized-shell-output.jsonl, and tests/fixtures/claude/settings/oversized-bash-output-length.json corpus so the TS and Rust suites assert against the same bytes.

Closes #271


Generated by Claude Code

claude and others added 2 commits May 5, 2026 01:48
Port `packages/analyze/src/tool-output-bloat.ts` to the Rust analyze
crate. Brings up both signals — Claude `BASH_MAX_OUTPUT_LENGTH`
static-config check (with `~/.claude/settings.json` and
`<cwd>/.claude/settings.json` loader) and cross-harness observed
oversized `tool_result` aggregation — plus the `WasteFinding` adapter
for both kinds.

Public surface mirrors `@relayburn/analyze`: `BASH_MAX_OUTPUT_ENV_KEY`,
`DEFAULT_BLOAT_TOKEN_THRESHOLD`, `detect_observed_bloat`,
`detect_static_config_bloat`, `detect_tool_output_bloat`,
`load_claude_settings`, `project_claude_settings_path`,
`user_claude_settings_path`, `tool_output_bloat_to_finding`, plus
`ToolOutputBloat`, `ToolOutputBloatKind`, `ClaudeSettings`,
`LoadedClaudeSettings`, and the three `Detect*Options` request types.

Foundation crate is sync, so the loader uses `std::fs::read_to_string`
and returns `Option<LoadedClaudeSettings>` (None for missing or
malformed JSON, matching the TS `undefined` behavior). Tests cover both
files-present and files-absent fixtures via `tempfile::TempDir`, the
shared TS/Rust fixture corpus under `tests/fixtures/`, and the unit
cases mirrored from `tool-output-bloat.test.ts`.

Closes #271
…1-webhooks-Di7rr

# Conflicts:
#	crates/relayburn-analyze/Cargo.toml
#	crates/relayburn-analyze/src/lib.rs
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6abe0b4c-bdc6-45f6-8fef-9fb0908e3f1b

📥 Commits

Reviewing files that changed from the base of the PR and between b917d48 and 7a25909.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • crates/relayburn-analyze/src/lib.rs
  • crates/relayburn-analyze/src/tool_output_bloat.rs

📝 Walkthrough

Walkthrough

This pull request ports the tool-output-bloat detector from TypeScript to Rust within the relayburn-analyze crate. It detects oversized tool outputs via two signals: static Claude settings configuration checks and observed event-stream evidence aggregation. Both signals are unified into a single data model and converted to bloat findings.

Changes

Tool Output Bloat Detector

Layer / File(s) Summary
Data Shape & Constants
crates/relayburn-analyze/src/tool_output_bloat.rs (lines 1–123)
Introduces ToolOutputBloatKind enum (StaticConfig vs. ObservedBloat), ToolOutputBloat struct with source/kind/toolName/limits/evidence/cost fields, ClaudeSettings and LoadedClaudeSettings types for settings file representation, and constants BASH_MAX_OUTPUT_ENV_KEY, DEFAULT_BLOAT_TOKEN_THRESHOLD.
Helper Functions
crates/relayburn-analyze/src/tool_output_bloat.rs (lines 62–101)
Adds utilities: bytes_to_tokens (bytes÷4 heuristic), severity_from_usd (cost scaling), fmt_usd (formatting), format_with_commas (display).
Settings Loading
crates/relayburn-analyze/src/tool_output_bloat.rs (lines 129–177)
Implements user_claude_settings_path(), project_claude_settings_path(), and load_claude_settings() to read and merge Claude settings from ~/.claude/settings.json and local .claude/settings.json with graceful missing-file handling.
Signal A: Static Config Detection
crates/relayburn-analyze/src/tool_output_bloat.rs (lines 179–262)
Implements DetectStaticConfigBloatOptions and detect_static_config_bloat() to flag when configured BASH_MAX_OUTPUT_LENGTH exceeds threshold, including parse_int_lenient() for permissive integer parsing.
Signal B: Observed Bloat Detection
crates/relayburn-analyze/src/tool_output_bloat.rs (lines 268–477)
Implements DetectObservedBloatOptions and detect_observed_bloat() with internal helpers (ToolUseLookup, build_lookup, percentile) to scan tool_result events, bucket by (source, toolName), compute p95 thresholds, aggregate evidence across sessions, and estimate carry cost using pricing.
Orchestration & Adapter
crates/relayburn-analyze/src/tool_output_bloat.rs (lines 482–647)
Adds DetectToolOutputBloatOptions and detect_tool_output_bloat() orchestrator that conditionally runs both signals and concatenates results. Implements tool_output_bloat_to_finding() to convert bloat records to WasteFinding with actionable remediation guidance (paste/instruction targets).
Module Exports
crates/relayburn-analyze/src/lib.rs (lines 29–106)
Declares pub mod tool_output_bloat and re-exports public API: detection functions, settings loaders, option structs, data types, and constants.
Tests & Documentation
crates/relayburn-analyze/src/tool_output_bloat.rs (lines 649–1611), CHANGELOG.md (line 7)
Comprehensive unit tests covering static-config parsing/thresholding/precedence, observed-bloat bucketing/aggregation, orchestration, finding conversion, and fixture-driven end-to-end scenarios. Changelog documents the cross-package release note.

Sequence Diagram

sequenceDiagram
    participant User
    participant Loader as Settings Loader
    participant StaticSig as Signal A:<br/>Static Config
    participant EventScan as Signal B:<br/>Observed Events
    participant Orchest as Orchestrator
    participant Adapter as Finding Adapter
    participant Output as Bloat Findings

    User->>Loader: load_claude_settings(paths)
    Loader->>Loader: read & parse ~/.claude/settings.json<br/>+ ./.claude/settings.json
    Loader-->>User: LoadedClaudeSettings

    User->>Orchest: detect_tool_output_bloat(opts)
    
    Orchest->>StaticSig: detect_static_config_bloat(opts)
    StaticSig->>StaticSig: check BASH_MAX_OUTPUT_LENGTH<br/>against threshold
    StaticSig-->>Orchest: Vec<ToolOutputBloat> [static]

    Orchest->>EventScan: detect_observed_bloat(opts)
    EventScan->>EventScan: scan tool_result events<br/>aggregate by (source, toolName)<br/>compute p95 + pricing cost
    EventScan-->>Orchest: Vec<ToolOutputBloat> [observed]

    Orchest->>Orchest: concat + sort by cost desc
    Orchest-->>User: Vec<ToolOutputBloat>

    User->>Adapter: tool_output_bloat_to_finding(bloat)
    Adapter->>Adapter: format remediation:<br/>- static: paste safe BASH_MAX_OUTPUT_LENGTH<br/>- observed: filter/pagination advice
    Adapter-->>Output: WasteFinding
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Poem

🐰 A detector most keen,
Scans settings unseen,
Bloat findings arise—
No shell-script surprise!
Pricing the cost, clean and lean.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: porting the tool-output-bloat detector to the Rust relayburn-analyze crate.
Description check ✅ Passed The description provides comprehensive detail about the port, covering both signal sources, the API surface, implementation notes, and test coverage, all directly related to the changes.
Linked Issues check ✅ Passed The PR implementation fully addresses all coding requirements from issue #271: exports constants, types, and functions; implements all three detectors; includes the settings loader with proper file handling; and passes conformance tests.
Out of Scope Changes check ✅ Passed All changes directly support the tool-output-bloat detector port: CHANGELOG updates, lib.rs module registration, and the complete tool_output_bloat.rs implementation with no extraneous modifications.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/close-issue-271-webhooks-Di7rr

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

@willwashburn willwashburn merged commit ce1a3f2 into main May 5, 2026
3 checks passed
@willwashburn willwashburn deleted the claude/close-issue-271-webhooks-Di7rr branch May 5, 2026 11:36
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.

[Rust port] relayburn-analyze: tool-output-bloat detector

2 participants