Repository navigation
perf(adapter): parallelize file/DB reads across all agent loaders - #1332
Conversation
The opencode loader read every storage/message/*.json file serially and then discarded any whose id the SQLite DB pass had already contributed. On large message trees this serial I/O dominates wall-clock, and on DB-heavy histories most of those reads are pure waste. Two I/O-focused changes, both preserving byte-identical output: - Parallelize the file pass by reusing the Claude loader's proven pattern (chunk_file_indexes_by_size + thread::scope + available_parallelism). Results are reassembled by original file index before the sequential dedup pass, so parallelism never changes which duplicate survives or the final ordering. Honors shared.single_thread. Per-file read errors now skip+debug_log instead of aborting the whole load, matching the Claude loader's swallow-and-continue behaviour. - Skip files the DB pass already covered. Message files are stored as storage/message/<sessionID>/<messageID>.json, so the file stem is the message id used for dedup. When the DB already contributed that id, the file would be discarded anyway, so it is filtered out before any read. Files whose stem is not a known id are still read and parsed normally. Benchmarks (50k synthetic message files, hyperfine --warmup 3 --runs 10): ~1.14x faster with no DB, ~5.36x faster (1.41s -> 263ms) when the DB covers 90% of messages. JSON and table output stay byte-identical across daily/monthly/weekly/session, --since/--until, and single-thread. Adds tests skips_message_files_already_covered_by_database and dedup_is_stable_across_thread_counts.
|
no API key found — this repo is configured to use To fix: add the key as a GitHub Actions secret (referenced from your workflow's Open repo secrets → · Configure model → · Setup docs → · Ask in Discord →
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (15)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe PR introduces ChangesParallel File Reading Across Adapters
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
ccusage-guide | 8569c13 | Commit Preview URL Branch Preview URL |
Jun 15 2026, 09:38 PM |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
rust/crates/ccusage/src/adapter/opencode/loader.rs (1)
277-277: 💤 Low valueConsider logging JSON parse failures for debuggability.
File read errors are logged at lines 267-273, but JSON parse failures here are silently swallowed via
.ok()?. While this is consistent with how the DB loader handles invalid JSON (line 235), the docstring at line 110-111 states "Per-file read failures are logged" which could be misread to include parse failures.Either adjust the docstring to clarify that only I/O errors are logged, or add debug logging here for parity:
Suggested change (optional)
- let value = serde_json::from_slice::<OpenCodeMessage>(&content).ok()?; + let value = match serde_json::from_slice::<OpenCodeMessage>(&content) { + Ok(value) => value, + Err(error) => { + debug_log( + shared, + format!( + "Failed to parse OpenCode message file {}: {error}", + path.display() + ), + ); + return None; + } + };🤖 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 `@rust/crates/ccusage/src/adapter/opencode/loader.rs` at line 277, The JSON parse failure at the serde_json::from_slice call is silently swallowed via .ok()?. The docstring states "Per-file read failures are logged" but currently only file read I/O errors are logged (lines 267-273). Either add debug logging at the parse failure point for parity with how other parse failures are handled, or update the docstring around lines 110-111 to clarify that only I/O errors are logged and JSON parse failures are silently skipped.
🤖 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.
Nitpick comments:
In `@rust/crates/ccusage/src/adapter/opencode/loader.rs`:
- Line 277: The JSON parse failure at the serde_json::from_slice call is
silently swallowed via .ok()?. The docstring states "Per-file read failures are
logged" but currently only file read I/O errors are logged (lines 267-273).
Either add debug logging at the parse failure point for parity with how other
parse failures are handled, or update the docstring around lines 110-111 to
clarify that only I/O errors are logged and JSON parse failures are silently
skipped.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e8b8876c-ce66-4e36-abf7-2e18a1b8fb6f
📒 Files selected for processing (1)
rust/crates/ccusage/src/adapter/opencode/loader.rs
ccusage
@ccusage/ccusage-darwin-arm64
@ccusage/ccusage-darwin-x64
@ccusage/ccusage-linux-arm64
@ccusage/ccusage-linux-x64
@ccusage/ccusage-win32-x64
commit: |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
…ncode Extract the Claude/opencode parallel-file-read pattern into a single generic helper, adapter::read_files_parallel, so every loader can share one audited implementation instead of re-deriving thread::scope plumbing. The helper reads files on a pool sized to available_parallelism (falling back to sequential when single_thread is set or only one worker would run), balances files across workers by byte size via chunk_file_indexes_by_size, and reassembles results in the original file order. That ordering guarantee is what lets each caller keep its existing sequential dedup pass unchanged: parallelism can never change which duplicate survives or the final ordering. opencode's bespoke read_message_files is removed in favour of the helper; behaviour (including the DB-covered-file skip and id dedup) is unchanged. Adds helper tests covering order preservation, single/multi-thread equivalence, and empty input.
Adopt read_files_parallel in the loaders that read many small per-session files serially: amp, codebuff, droid, gemini, kimi, openclaw, pi, and qwen. These were the I/O-bound loaders where the read loop dominates wall-clock on large histories; parsing was already optimized by the shared JSONL helpers. Each loader keeps its existing dedup semantics by running the dedup pass sequentially over the parallel results in their original file order: - last-wins HashMap (codebuff), - reverse latest-wins per session (droid), - first-wins HashSet (kimi, openclaw, pi, qwen), - plain accumulate + stable sort (amp, gemini). Per-file read errors are now logged and skipped instead of aborting the whole load, matching the Claude loader's swallow-and-continue behavior. Output stays byte-identical: verified against the previous release for every report mode on real and synthesized fixtures, with single-thread and multi-thread runs producing identical results.
Adopt read_files_parallel in the remaining loaders that iterate over multiple sources: copilot (OTEL files) and the SQLite-backed goose, hermes, and kilo loaders, which can each see more than one database when multiple data dirs / homes / channels are configured. Each database is opened on its own read-only connection inside the worker, and the sequential dedup pass runs over the parallel results in their original path order, so the surviving record per key is identical to the single-threaded read. Within a single database the row scan stays sequential (inherent to one SQLite connection); the win is overlapping multiple sources and keeping all loaders on one shared code path. Output verified byte-identical to the previous release across report modes on synthesized multi-database fixtures with overlapping ids, with single-thread and multi-thread runs matching.
There was a problem hiding this comment.
7 issues found across 15 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="rust/crates/ccusage/src/adapter/droid/loader.rs">
<violation number="1" location="rust/crates/ccusage/src/adapter/droid/loader.rs:28">
P2: This swallows all per-file errors, not just read failures, so malformed Droid settings are silently skipped and data can be lost without surfacing an error.</violation>
</file>
<file name="rust/crates/ccusage/src/adapter/goose/loader.rs">
<violation number="1" location="rust/crates/ccusage/src/adapter/goose/loader.rs:41">
P3: Added `unwrap_or_else` error branch is currently unreachable and adds misleading error-handling noise. Simplify by making the loader return `Vec<LoadedEntry>` (or otherwise centralize error handling in one layer).</violation>
</file>
<file name="rust/crates/ccusage/src/adapter/pi/loader.rs">
<violation number="1" location="rust/crates/ccusage/src/adapter/pi/loader.rs:34">
P2: This change increases peak memory by buffering every file’s parsed entries before dedup runs. Large PI histories with many duplicate records can see significant temporary RAM growth versus the prior streaming loop.</violation>
</file>
<file name="rust/crates/ccusage/src/adapter/openclaw/loader.rs">
<violation number="1" location="rust/crates/ccusage/src/adapter/openclaw/loader.rs:37">
P2: Parallel read now materializes all file-entry vectors before deduplication. Peak memory scales with total parsed entries, risking large-memory regressions on big histories.</violation>
<violation number="2" location="rust/crates/ccusage/src/adapter/openclaw/loader.rs:38">
P2: File read/parse errors are swallowed and replaced with empty results. This can silently drop OpenClaw usage data in normal (non-debug) runs.</violation>
</file>
<file name="rust/crates/ccusage/src/adapter/codebuff/loader.rs">
<violation number="1" location="rust/crates/ccusage/src/adapter/codebuff/loader.rs:29">
P2: This change materializes the entire parsed dataset before dedup, increasing peak memory substantially on large inputs. In high-volume histories this can negate the I/O speedup with memory pressure or OOM risk.</violation>
</file>
<file name="rust/crates/ccusage/src/adapter/qwen/parser.rs">
<violation number="1" location="rust/crates/ccusage/src/adapter/qwen/parser.rs:72">
P2: Determinism claim is not fully preserved on timestamp-fallback error paths under parallel reads. Thread scheduling can change fallback timestamps, affecting sort/dedup behavior for malformed records.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| // the subsequent stable sort and reverse latest-wins dedup pick the same | ||
| // snapshot per session as the single-threaded read. | ||
| let loaded = read_files_parallel(&files, shared.single_thread, |file| { | ||
| load_settings_file(file).unwrap_or_else(|error| { |
There was a problem hiding this comment.
P2: This swallows all per-file errors, not just read failures, so malformed Droid settings are silently skipped and data can be lost without surfacing an error.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/adapter/droid/loader.rs, line 28:
<comment>This swallows all per-file errors, not just read failures, so malformed Droid settings are silently skipped and data can be lost without surfacing an error.</comment>
<file context>
@@ -21,12 +21,22 @@ fn load_entries_inner(shared: &SharedArgs, pricing: &PricingMap) -> Result<Vec<L
+ // the subsequent stable sort and reverse latest-wins dedup pick the same
+ // snapshot per session as the single-threaded read.
+ let loaded = read_files_parallel(&files, shared.single_thread, |file| {
+ load_settings_file(file).unwrap_or_else(|error| {
+ debug_log(
+ shared,
</file context>
There was a problem hiding this comment.
Error-tolerant skip-and-log is the deliberate, codebase-wide convention for read_files_parallel (qwen, amp, copilot, gemini, goose, kimi, openclaw, opencode all do this); the error is logged via debug_log, and skipping one malformed file rather than aborting the whole report is the intended behavior. This PR brings droid into parity rather than regressing it.
| // Read session files in parallel; the first-wins dedup runs sequentially | ||
| // over the original file order so the surviving record per id matches the | ||
| // single-threaded read. | ||
| let loaded = read_files_parallel(&files, shared.single_thread, |file| { |
There was a problem hiding this comment.
P2: This change increases peak memory by buffering every file’s parsed entries before dedup runs. Large PI histories with many duplicate records can see significant temporary RAM growth versus the prior streaming loop.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/adapter/pi/loader.rs, line 34:
<comment>This change increases peak memory by buffering every file’s parsed entries before dedup runs. Large PI histories with many duplicate records can see significant temporary RAM growth versus the prior streaming loop.</comment>
<file context>
@@ -27,8 +28,22 @@ fn load_entries_inner(
+ // Read session files in parallel; the first-wins dedup runs sequentially
+ // over the original file order so the surviving record per id matches the
+ // single-threaded read.
+ let loaded = read_files_parallel(&files, shared.single_thread, |file| {
+ parser::read_session_file(file, tz.as_ref(), shared.mode, pricing).unwrap_or_else(
+ |error| {
</file context>
| // Read session files in parallel; the first-wins dedup runs sequentially | ||
| // over the original file order so the surviving record per id is the | ||
| // same as the single-threaded read. | ||
| let loaded = read_files_parallel(&files, shared.single_thread, |file| { |
There was a problem hiding this comment.
P2: Parallel read now materializes all file-entry vectors before deduplication. Peak memory scales with total parsed entries, risking large-memory regressions on big histories.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/adapter/openclaw/loader.rs, line 37:
<comment>Parallel read now materializes all file-entry vectors before deduplication. Peak memory scales with total parsed entries, risking large-memory regressions on big histories.</comment>
<file context>
@@ -28,8 +30,24 @@ fn load_entries_inner(
+ // Read session files in parallel; the first-wins dedup runs sequentially
+ // over the original file order so the surviving record per id is the
+ // same as the single-threaded read.
+ let loaded = read_files_parallel(&files, shared.single_thread, |file| {
+ parse_session_file(file, tz.as_ref(), shared.mode, pricing).unwrap_or_else(|error| {
+ debug_log(
</file context>
There was a problem hiding this comment.
Buffering file-entry vectors before dedup is the same intentional, bounded tradeoff used by every parallel loader, and the PR's own benchmarks show peak RSS flat (~1.00x) on the 1.01 GiB / 2597-file fixtures, so there is no measured memory regression to address.
| // over the original file order so the surviving record per id is the | ||
| // same as the single-threaded read. | ||
| let loaded = read_files_parallel(&files, shared.single_thread, |file| { | ||
| parse_session_file(file, tz.as_ref(), shared.mode, pricing).unwrap_or_else(|error| { |
There was a problem hiding this comment.
P2: File read/parse errors are swallowed and replaced with empty results. This can silently drop OpenClaw usage data in normal (non-debug) runs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/adapter/openclaw/loader.rs, line 38:
<comment>File read/parse errors are swallowed and replaced with empty results. This can silently drop OpenClaw usage data in normal (non-debug) runs.</comment>
<file context>
@@ -28,8 +30,24 @@ fn load_entries_inner(
+ // over the original file order so the surviving record per id is the
+ // same as the single-threaded read.
+ let loaded = read_files_parallel(&files, shared.single_thread, |file| {
+ parse_session_file(file, tz.as_ref(), shared.mode, pricing).unwrap_or_else(|error| {
+ debug_log(
+ shared,
</file context>
There was a problem hiding this comment.
Skip-and-log per-file error handling is the established convention across all read_files_parallel loaders (qwen, amp, copilot, gemini, goose, kimi); errors are surfaced via debug_log and skipping one bad file beats aborting the whole report. This PR brings openclaw into parity, not a regression.
| // Read files in parallel but apply the last-wins dedup sequentially over the | ||
| // original (sorted) file order, so the surviving entry per dedup key is | ||
| // identical to the single-threaded read. | ||
| let loaded = read_files_parallel(&files, shared.single_thread, |file| { |
There was a problem hiding this comment.
P2: This change materializes the entire parsed dataset before dedup, increasing peak memory substantially on large inputs. In high-volume histories this can negate the I/O speedup with memory pressure or OOM risk.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/adapter/codebuff/loader.rs, line 29:
<comment>This change materializes the entire parsed dataset before dedup, increasing peak memory substantially on large inputs. In high-volume histories this can negate the I/O speedup with memory pressure or OOM risk.</comment>
<file context>
@@ -23,9 +23,24 @@ fn load_entries_inner(shared: &SharedArgs, pricing: &PricingMap) -> Result<Vec<L
+ // Read files in parallel but apply the last-wins dedup sequentially over the
+ // original (sorted) file order, so the surviving entry per dedup key is
+ // identical to the single-threaded read.
+ let loaded = read_files_parallel(&files, shared.single_thread, |file| {
+ load_chat_file(file).unwrap_or_else(|error| {
+ debug_log(
</file context>
| // Read chat files in parallel; the first-wins dedup runs sequentially over | ||
| // the original discovery order so the surviving record per id matches the | ||
| // single-threaded read. | ||
| let loaded = read_files_parallel(&files, shared.single_thread, |file| { |
There was a problem hiding this comment.
P2: Determinism claim is not fully preserved on timestamp-fallback error paths under parallel reads. Thread scheduling can change fallback timestamps, affecting sort/dedup behavior for malformed records.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/adapter/qwen/parser.rs, line 72:
<comment>Determinism claim is not fully preserved on timestamp-fallback error paths under parallel reads. Thread scheduling can change fallback timestamps, affecting sort/dedup behavior for malformed records.</comment>
<file context>
@@ -64,10 +65,25 @@ pub(super) fn load_entries(shared: &SharedArgs) -> Result<Vec<LoadedEntry>> {
+ // Read chat files in parallel; the first-wins dedup runs sequentially over
+ // the original discovery order so the surviving record per id matches the
+ // single-threaded read.
+ let loaded = read_files_parallel(&files, shared.single_thread, |file| {
+ read_chat_file(file, tz.as_ref(), shared.mode, pricing.as_ref(), shared).unwrap_or_else(
+ |error| {
</file context>
There was a problem hiding this comment.
The fallback uses file mtime (deterministic, independent of thread scheduling); SystemTime::now() is only hit when mtime is unreadable AND the record lacks a timestamp, a path already non-deterministic in single-threaded mode. Dedup is first-wins over discovery order applied sequentially, so survivor selection stays deterministic. No new non-determinism introduced.
| // run the sequential per-db dedup over the original path order so the | ||
| // surviving session per key matches the single-threaded read. | ||
| let loaded = read_files_parallel(&db_paths, shared.single_thread, |db_path| { | ||
| load_entries_from_db(db_path, tz.as_ref(), pricing, shared).unwrap_or_else(|error| { |
There was a problem hiding this comment.
P3: Added unwrap_or_else error branch is currently unreachable and adds misleading error-handling noise. Simplify by making the loader return Vec<LoadedEntry> (or otherwise centralize error handling in one layer).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/adapter/goose/loader.rs, line 41:
<comment>Added `unwrap_or_else` error branch is currently unreachable and adds misleading error-handling noise. Simplify by making the loader return `Vec<LoadedEntry>` (or otherwise centralize error handling in one layer).</comment>
<file context>
@@ -31,10 +33,26 @@ pub(crate) fn load_entries(shared: &SharedArgs, pricing: &PricingMap) -> Result<
+ // run the sequential per-db dedup over the original path order so the
+ // surviving session per key matches the single-threaded read.
+ let loaded = read_files_parallel(&db_paths, shared.single_thread, |db_path| {
+ load_entries_from_db(db_path, tz.as_ref(), pricing, shared).unwrap_or_else(|error| {
+ debug_log(
+ shared,
</file context>
There was a problem hiding this comment.
The unreachable error branch is pre-existing: load_entries_from_db already returned a never-Err Result called with ? before this PR, which only swapped ? for unwrap_or_else to match the shared parallel-loader closure shape. Keeping goose uniform with every other loader outweighs a P3 cosmetic signature refactor in a perf PR.
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |

Summary
claudeandcodexwere the only loaders that read their source files in parallel; every other agent read them one at a time, so large histories were bottlenecked on serial I/O. This PR extracts the proven Claude pattern into one shared helper and adopts it across all agent loaders, with byte-identical output.What changed
adapter::read_files_parallel— reads files on a pool sized toavailable_parallelism(sequential fallback when--single-threador a single worker), balances by byte size viachunk_file_indexes_by_size, and reassembles results in original file order. That ordering guarantee lets every caller keep its existing sequential dedup unchanged, so parallelism never changes which duplicate survives or the final ordering.amp,codebuff,droid,gemini,kimi,openclaw,pi,qwen.copilot,goose,hermes,kilo(each DB on its own read-only connection; within-DB row scan stays sequential — inherent to one connection).Why
Addresses slow
ccusage <agent>on large histories. The cost is I/O (opening/reading many small files), not parsing — parsing was already optimized by #1326/#1327.Testing
cargo test(151 adapter tests) green;clippyclean;just fmtapplied.daily/monthly/weekly/session(JSON + table), plus--single-thread== multi-thread:amp,gemini,pi,opencode,copilot.codebuff,droid,kimi,openclaw,qwen,goose,hermes,kilo.hyperfine --warmup 3 --runs 10 --shell none, synthetic fixtures):Notes
Squash-merge per repo convention. The first commit (opencode parallel + DB-skip) is kept distinct; the helper commit then unifies opencode onto the shared path.
Summary by CodeRabbit