Skip to content

feat: add compare-heapsnapshots command#15

Merged
aeroxy merged 17 commits into
mainfrom
dev
Jul 11, 2026
Merged

feat: add compare-heapsnapshots command#15
aeroxy merged 17 commits into
mainfrom
dev

Conversation

@aeroxy

@aeroxy aeroxy commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added an offline compare-heapsnapshots command to diff two local .heapsnapshot files, showing a class-diff summary by default and optional per-class detail via --class-index (deterministic ordering).
  • Bug Fixes

    • Improved heapsnapshot temp-file cleanup, parsing/validation, and consistent CSV-style text escaping.
    • Updated kill-daemon to support --force, require interactive confirmation, and prevent non-interactive use unless --force is set.
    • Bounded Chrome connection handshake with clearer guidance on likely human-approval delays.
  • Documentation

    • Expanded agent/offline command guidance and corrected connection-failure retry instructions.
  • Tests

    • Added/expanded unit and end-to-end coverage for offline comparison, class diffs, and --class-index validation.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d10e293-b185-4633-af36-a6bfe8a6a0b1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an offline compare-heapsnapshots subcommand, bounds Chrome connection handshakes, changes kill-daemon handling, updates related docs, and reflows existing command and test code.

Changes

Compare Heapsnapshots Feature

Layer / File(s) Summary
Snapshot parsing and class diffing
src/commands/memory.rs
Adds validated snapshot parsing, per-class aggregation, deterministic diffs, text and structured rendering, temporary-file cleanup, and offline comparison execution.
Compare heapsnapshot validation
src/commands/memory.rs
Adds fixtures and tests for schema validation, aggregation, diff behavior, rendering indices, CSV escaping, and invalid class selection.
CLI subcommand wiring
src/lib.rs, src/commands/executor.rs
Registers compare-heapsnapshots arguments and routes the command through offline execution before daemon or Chrome connection handling.

Daemon Connection and Kill Handling

Layer / File(s) Summary
Chrome connect timeout
src/cdp.rs, AGENTS.md, skill/chrome-devtools/SKILL.md
Adds configurable handshake timeout parsing, timeout-specific errors, tests, and matching connection-failure guidance.
Kill daemon decision flow
src/lib.rs, src/commands/executor.rs, AGENTS.md, skill/chrome-devtools/SKILL.md
Adds --force, centralizes proceed-or-prompt decisions, and handles refusal or confirmation before daemon signaling.

Formatting Reflows

Layer / File(s) Summary
Formatting and supporting updates
src/commands/evaluate.rs, src/commands/read_page.rs, src/commands/screenshot.rs, src/commands/third_party.rs, src/lib.rs, src/commands/executor.rs, src/commands/memory.rs, AGENTS.md, skill/chrome-devtools/SKILL.md
Reflows existing expressions, assertions, helper lists, argument extraction, and documentation without changing the described behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Poem

A rabbit compares heaps tonight,
With classes sorted neat and right.
Chrome waits, but won’t hang long;
Daemons ask before they’re gone.
“Hop once, then seek human sight!” 🐇

🚥 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 accurately summarizes the main change: adding the new compare-heapsnapshots command.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

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.

@aeroxy

aeroxy commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new offline command, compare-heapsnapshots, which allows users to compare two .heapsnapshot files and view per-class differences in added/deleted nodes and size deltas without requiring a Chrome connection. It implements the necessary parsing, aggregation, diffing, and formatting logic, along with comprehensive unit tests. The feedback highlights a critical issue where sorting the diffs is non-deterministic for classes with identical size deltas due to the randomized iteration order of HashSet. This can cause the index used to query detailed class views to change across CLI invocations. To resolve this, a deterministic tie-breaker sorting by class name is recommended.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/commands/memory.rs Outdated

@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: 1

🧹 Nitpick comments (1)
src/lib.rs (1)

251-256: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer auto-derived short for consistency.

Elsewhere in this enum (e.g. InspectHeapSnapshotNode, output fields) short flags are auto-derived via #[arg(long, short)] rather than explicitly pinned. Since 'b'/'c' are exactly the auto-derived values here, using the same convention would be more consistent.

♻️ Proposed consistency tweak
         /// Path to the base (earlier) .heapsnapshot file
-        #[arg(long, short = 'b')]
+        #[arg(long, short)]
         base: String,
         /// Path to the current (later) .heapsnapshot file
-        #[arg(long, short = 'c')]
+        #[arg(long, short)]
         current: String,
🤖 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 `@src/lib.rs` around lines 251 - 256, The `base` and `current` fields in this
enum use explicit short flags even though they match clap’s auto-derived values.
Update the `#[arg(...)]` attributes on these fields to use the same `#[arg(long,
short)]` convention used by `InspectHeapSnapshotNode` and the `output` fields so
the enum stays consistent.
🤖 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 `@src/commands/memory.rs`:
- Line 399: The `diffs.sort_by` ordering in `memory.rs` is only comparing
`size_delta`, so ties can reorder differently across runs because
`all_names.into_iter()` comes from a randomized `HashSet`. Update the sorting in
the summary path to use a deterministic secondary key from the class identity
(for example the class name or another stable field from the `diff` item) so the
row order is reproducible across separate invocations and `--class-index`
matches the displayed summary index.

---

Nitpick comments:
In `@src/lib.rs`:
- Around line 251-256: The `base` and `current` fields in this enum use explicit
short flags even though they match clap’s auto-derived values. Update the
`#[arg(...)]` attributes on these fields to use the same `#[arg(long, short)]`
convention used by `InspectHeapSnapshotNode` and the `output` fields so the enum
stays consistent.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6d31b44-cc7b-4a10-a3ce-ddb0812f72d6

📥 Commits

Reviewing files that changed from the base of the PR and between b77678a and 273e4df.

📒 Files selected for processing (3)
  • src/commands/executor.rs
  • src/commands/memory.rs
  • src/lib.rs

Comment thread src/commands/memory.rs Outdated
@aeroxy

aeroxy commented Jul 4, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@aeroxy

aeroxy commented Jul 4, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new offline command, compare-heapsnapshots, which compares two .heapsnapshot files to report per-class added and deleted nodes without requiring a Chrome connection. It includes helper functions for parsing, aggregating, diffing, and formatting the snapshot data, along with comprehensive unit tests. The reviewer suggested two performance optimizations: first, avoiding redundant string cloning in build_class_aggregates by only cloning when inserting a new class name; second, optimizing diff_snapshots to eliminate an intermediate HashSet allocation and reduce map lookups by iterating over the maps directly.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/commands/memory.rs Outdated
Comment thread src/commands/memory.rs

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

🧹 Nitpick comments (2)
src/commands/memory.rs (2)

406-415: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse csv_escape in format_node_details to avoid divergent escaping.

This is a verbatim copy of the escaping logic already inlined in format_node_details (Lines 226-230). Having two copies invites future drift. Route the existing call through this helper.

♻️ Proposed fix
-        let escaped_name = if name.contains(',') || name.contains('"') || name.contains('\n') || name.contains('\r') {
-            format!("\"{}\"", name.replace('"', "\"\""))
-        } else {
-            name.to_string()
-        };
+        let escaped_name = csv_escape(name);
🤖 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 `@src/commands/memory.rs` around lines 406 - 415, `format_node_details`
currently duplicates the CSV escaping logic that `csv_escape` already provides,
which can lead to drift between the two paths. Update `format_node_details` to
call `csv_escape` for class/node name escaping instead of keeping its own inline
implementation, and leave `csv_escape` as the single shared helper for this
behavior.

355-397: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Detail node ordering is non-deterministic across runs.

added_ids/deleted_ids are populated by iterating cur.nodes / base_a.nodes, which are HashMaps seeded with a per-process random state. The summary sort is now stable (Line 399), but for a class with multiple added/removed nodes, the --class-index detail view rows (format_class_diff_detail) will appear in a different order on each separate invocation. Add a deterministic key so detail output is reproducible.

🔧 Proposed fix: sort node lists by id
         let added_count = added_ids.len();
         let removed_count = deleted_ids.len();
         if added_count == 0 && removed_count == 0 {
             return None;
         }
+        // Sort by id so the per-class detail view is reproducible across
+        // separate `--class-index` invocations (HashMap order is randomized).
+        let mut added: Vec<(u64, u64)> = added_ids.into_iter().zip(added_self_sizes).collect();
+        added.sort_unstable_by_key(|&(id, _)| id);
+        let (added_ids, added_self_sizes): (Vec<u64>, Vec<u64>) = added.into_iter().unzip();
+        let mut deleted: Vec<(u64, u64)> = deleted_ids.into_iter().zip(deleted_self_sizes).collect();
+        deleted.sort_unstable_by_key(|&(id, _)| id);
+        let (deleted_ids, deleted_self_sizes): (Vec<u64>, Vec<u64>) = deleted.into_iter().unzip();
         let count_delta = added_count as i64 - removed_count as i64;
🤖 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 `@src/commands/memory.rs` around lines 355 - 397, Make the per-class detail
rows deterministic in `memory.rs` by ensuring `HeapSnapshotClassDiff` stores
`added_ids` and `deleted_ids` in a stable order before
`format_class_diff_detail` renders them. The current loops that populate these
vectors from `cur_agg` and `base_agg` iterate `HashMap` entries, so sort the
collected node ids (and keep `added_self_sizes` / `deleted_self_sizes` aligned
with them) using a consistent key such as the node id or a derived deterministic
tuple. This should be done in the aggregation logic that builds
`HeapSnapshotClassDiff`, not in the formatter, so `--class-index` output is
reproducible across runs.
🤖 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 `@src/commands/memory.rs`:
- Around line 406-415: `format_node_details` currently duplicates the CSV
escaping logic that `csv_escape` already provides, which can lead to drift
between the two paths. Update `format_node_details` to call `csv_escape` for
class/node name escaping instead of keeping its own inline implementation, and
leave `csv_escape` as the single shared helper for this behavior.
- Around line 355-397: Make the per-class detail rows deterministic in
`memory.rs` by ensuring `HeapSnapshotClassDiff` stores `added_ids` and
`deleted_ids` in a stable order before `format_class_diff_detail` renders them.
The current loops that populate these vectors from `cur_agg` and `base_agg`
iterate `HashMap` entries, so sort the collected node ids (and keep
`added_self_sizes` / `deleted_self_sizes` aligned with them) using a consistent
key such as the node id or a derived deterministic tuple. This should be done in
the aggregation logic that builds `HeapSnapshotClassDiff`, not in the formatter,
so `--class-index` output is reproducible across runs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b406a0b9-24f9-4db0-ba3b-4a8e6cd7310c

📥 Commits

Reviewing files that changed from the base of the PR and between b77678a and 612a303.

📒 Files selected for processing (3)
  • src/commands/executor.rs
  • src/commands/memory.rs
  • src/lib.rs

- Reformatted argument lists and chained method calls for better clarity in `executor.rs`, `memory.rs`, `read_page.rs`, `screenshot.rs`, `third_party.rs`, and `lib.rs`.
- Enhanced error handling messages in `lib.rs` for clearer debugging.
- Updated tests in `read_page.rs`, `screenshot.rs`, and `lib.rs` for improved readability and consistency in assertions.
- Removed unnecessary line breaks and adjusted formatting in various files to adhere to style guidelines.
@aeroxy

aeroxy commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@aeroxy

aeroxy commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new offline command, compare-heapsnapshots, which compares two local .heapsnapshot files and reports per-class added or deleted nodes without requiring a Chrome connection. It also includes extensive code formatting and style cleanups across several files. The review feedback highlights two important performance and memory optimizations in src/commands/memory.rs: dropping the raw HeapSnapshot ASTs immediately after aggregation to prevent potential Out-Of-Memory (OOM) errors on large snapshots, and pre-allocating vector capacities during the diffing process to minimize reallocations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/commands/memory.rs Outdated
Comment thread src/commands/memory.rs Outdated

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

🧹 Nitpick comments (1)
src/commands/memory.rs (1)

333-339: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider the Entry API to simplify the insert-or-update branch.

The get_mut/else-insert pattern duplicates the agg.nodes.insert(id, self_size) logic across both branches. entry(...).or_insert_with(...) collapses this to one line without behavior change.

♻️ Proposed refactor
-        if let Some(agg) = aggregates.get_mut(name_ref) {
-            agg.nodes.insert(id, self_size);
-        } else {
-            let mut agg = ClassAggregate::new();
-            agg.nodes.insert(id, self_size);
-            aggregates.insert(name_ref.clone(), agg);
-        }
+        aggregates
+            .entry(name_ref.clone())
+            .or_insert_with(ClassAggregate::new)
+            .nodes
+            .insert(id, self_size);
🤖 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 `@src/commands/memory.rs` around lines 333 - 339, The insert-or-update logic in
the aggregate handling duplicates the same node insertion in both branches.
Refactor the `aggregates` update in `memory.rs` to use the `Entry` API on
`name_ref` so `ClassAggregate::new()` is only created when missing and
`agg.nodes.insert(id, self_size)` happens in one place. Keep the existing
behavior unchanged while simplifying the branch around the `get_mut`/insert
path.
🤖 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 `@src/commands/memory.rs`:
- Around line 333-339: The insert-or-update logic in the aggregate handling
duplicates the same node insertion in both branches. Refactor the `aggregates`
update in `memory.rs` to use the `Entry` API on `name_ref` so
`ClassAggregate::new()` is only created when missing and `agg.nodes.insert(id,
self_size)` happens in one place. Keep the existing behavior unchanged while
simplifying the branch around the `get_mut`/insert path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 36e8f73a-3cf5-4170-be03-af9aa790539c

📥 Commits

Reviewing files that changed from the base of the PR and between b77678a and 9f5098e.

📒 Files selected for processing (7)
  • src/commands/evaluate.rs
  • src/commands/executor.rs
  • src/commands/memory.rs
  • src/commands/read_page.rs
  • src/commands/screenshot.rs
  • src/commands/third_party.rs
  • src/lib.rs

* preallocate added/deleted vectors using upper-bound capacities to avoid reallocations
* compute base length once to size deletion buffers efficiently
* scope snapshot parsing to drop raw data immediately after building aggregates, lowering peak memory usage during diff
@aeroxy

aeroxy commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@aeroxy

aeroxy commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

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

🧹 Nitpick comments (1)
src/commands/memory.rs (1)

316-342: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Use entry() to avoid double hashmap lookups in the hot loop.

get_mut followed by insert performs two lookups per node; entry() does it in one. This loop runs once per node in the entire snapshot, so for large snapshots this adds up.

♻️ Proposed refactor
-        if let Some(agg) = aggregates.get_mut(name_ref) {
-            agg.nodes.insert(id, self_size);
-        } else {
-            let mut agg = ClassAggregate::new();
-            agg.nodes.insert(id, self_size);
-            aggregates.insert(name_ref.clone(), agg);
-        }
+        aggregates
+            .entry(name_ref.clone())
+            .or_insert_with(ClassAggregate::new)
+            .nodes
+            .insert(id, self_size);
🤖 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 `@src/commands/memory.rs` around lines 316 - 342, The node aggregation loop in
`src/commands/memory.rs` is doing two HashMap lookups per class name via
`aggregates.get_mut()` followed by `insert`; switch this hot path to
`HashMap::entry()` in the `while current_idx + node_size <= nodes.len()` loop so
each `name_ref` is handled with a single lookup. Update the `ClassAggregate`
insertion logic to use the entry API while preserving the current
`ClassAggregate::new()` initialization and `agg.nodes.insert(id, self_size)`
behavior.
🤖 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 `@src/commands/memory.rs`:
- Around line 316-342: The node aggregation loop in `src/commands/memory.rs` is
doing two HashMap lookups per class name via `aggregates.get_mut()` followed by
`insert`; switch this hot path to `HashMap::entry()` in the `while current_idx +
node_size <= nodes.len()` loop so each `name_ref` is handled with a single
lookup. Update the `ClassAggregate` insertion logic to use the entry API while
preserving the current `ClassAggregate::new()` initialization and
`agg.nodes.insert(id, self_size)` behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 90584542-39aa-4394-8831-6c213c013471

📥 Commits

Reviewing files that changed from the base of the PR and between b77678a and e91d0ee.

📒 Files selected for processing (7)
  • src/commands/evaluate.rs
  • src/commands/executor.rs
  • src/commands/memory.rs
  • src/commands/read_page.rs
  • src/commands/screenshot.rs
  • src/commands/third_party.rs
  • src/lib.rs

…n termination

* add connection timeout for Chrome WebSocket handshake with actionable error messaging
* document failure handling and agent guidance in AGENTS.md and SKILL.md
* introduce guarded `kill-daemon` command with `--force` flag and TTY-based confirmation/refusal logic
* prevent non-interactive agents from killing daemon without explicit force
* add unit-testable decision logic for daemon termination behavior
* update CLI argument parsing and command handling for new `kill-daemon` behavior
@aeroxy

aeroxy commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@aeroxy

aeroxy commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new offline command compare-heapsnapshots to diff two .heapsnapshot files and report per-class added/deleted nodes, complete with text/JSON formatting and unit tests. It also implements a connection timeout for the Chrome WebSocket handshake to prevent indefinite hangs, and adds a safety guard to the kill-daemon command to refuse non-interactive execution unless --force is specified. The review feedback suggests a valuable performance optimization in the heap snapshot diffing logic to avoid allocating and sorting detailed node vectors when only the summary view is requested.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/commands/memory.rs
Comment thread src/commands/memory.rs Outdated

@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: 3

🧹 Nitpick comments (1)
src/commands/memory.rs (1)

57-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the redundant “what” comment.

Line 57 only restates the next CDP call; either drop it or replace it with the reason this enable step must precede streaming. As per coding guidelines, src/**/*.rs: Rust code comments should explain "why", not "what".

🤖 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 `@src/commands/memory.rs` around lines 57 - 59, The comment before the
HeapProfiler enable call is redundant and only restates what
`client.send_to_target(session_id, "HeapProfiler.enable", json!({}))` already
shows. Remove that “what” comment or replace it with a brief “why” explanation
for `send_to_target` in `memory.rs`, focusing on why HeapProfiler must be
enabled before starting streaming.

Source: Coding guidelines

🤖 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 `@src/commands/memory.rs`:
- Around line 652-658: The out-of-range classIndex validation in the memory
command is currently using a generic anyhow error, which will surface as an
unspecified CLI code. Update the invalid index path in the class selection logic
to return a typed CliError with ErrorCode::InvalidInput instead of anyhow!, so
the error boundary preserves the stable user-input error code; use the existing
classIndex/diffs lookup in memory command to locate the change.
- Around line 316-343: The flat node parsing in the memory snapshot aggregation
loop currently stops at the last complete record and silently ignores any
trailing partial data. Update the logic around the aggregates-building loop in
the memory command to validate that nodes.len() is evenly divisible by node_size
before iterating, and return an error for malformed snapshots instead of relying
on the current_idx + node_size <= nodes.len() check. Keep the fix localized to
the class aggregation path that uses ClassAggregate and the node offsets so
truncated arrays are rejected early.

In `@src/lib.rs`:
- Around line 874-876: The match on kill-daemon handling is partially moving cli
by destructuring cli.command, which breaks later uses of cli.command and
build_request(&cli) in the same flow. Update the Commands::KillDaemon branch to
borrow cli.command instead of taking ownership, and dereference the force field
inside the pattern so the existing cli value remains available for the later
matches and request-building logic.

---

Nitpick comments:
In `@src/commands/memory.rs`:
- Around line 57-59: The comment before the HeapProfiler enable call is
redundant and only restates what `client.send_to_target(session_id,
"HeapProfiler.enable", json!({}))` already shows. Remove that “what” comment or
replace it with a brief “why” explanation for `send_to_target` in `memory.rs`,
focusing on why HeapProfiler must be enabled before starting streaming.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e4a715b7-0eb2-443f-bd39-be280522faec

📥 Commits

Reviewing files that changed from the base of the PR and between b77678a and 2b3f4fd.

📒 Files selected for processing (10)
  • AGENTS.md
  • skill/chrome-devtools/SKILL.md
  • src/cdp.rs
  • src/commands/evaluate.rs
  • src/commands/executor.rs
  • src/commands/memory.rs
  • src/commands/read_page.rs
  • src/commands/screenshot.rs
  • src/commands/third_party.rs
  • src/lib.rs

Comment thread src/commands/memory.rs
Comment thread src/commands/memory.rs Outdated
Comment thread src/lib.rs Outdated
@aeroxy

aeroxy commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new offline compare-heapsnapshots command to compare two heap snapshot files and report per-class added or deleted nodes. It also adds safety guards to the kill-daemon command to prevent non-interactive agents from dropping approved Chrome connections, introduces a connection timeout for Chrome WebSocket handshakes to avoid indefinite hangs, and updates documentation. Feedback suggests extracting duplicate node field offset parsing logic in src/commands/memory.rs into a reusable helper struct to improve maintainability.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/commands/memory.rs
@aeroxy

aeroxy commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cfb3705f19

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/memory.rs Outdated
@aeroxy

aeroxy commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Jul 9, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Moved cli.command breaks build ✓ Resolved 🐞 Bug ≡ Correctness
Description
In src/lib.rs::run(), the kill-daemon early-intercept matches on cli.command by value, moving
the field out of cli. The function later matches on &cli.command for other commands, which will
not compile due to Rust’s partial-move rules.
Code

src/lib.rs[R874-876]

+    if let Commands::KillDaemon { force } = cli.command {
+        use std::io::IsTerminal;
+        match kill_daemon_decision(force, std::io::stdin().is_terminal()) {
Evidence
cli.command is moved in the kill-daemon branch, but later branches still borrow &cli.command,
which Rust forbids after a move.

src/lib.rs[873-875]
src/lib.rs[982-1012]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`run()` currently does `if let Commands::KillDaemon { force } = cli.command { ... }`, which moves `cli.command` out of `cli`. Later in the same function it still uses `&cli.command` (for offline intercepts like `inspect-heapsnapshot-node` / `compare-heapsnapshots`), which causes a compile-time partial-move error.

## Issue Context
This is a build-blocking ownership bug introduced by the new `KillDaemon { force: bool }` variant handling.

## Fix
Change the kill-daemon intercept to match by reference, e.g.:
- `if let Commands::KillDaemon { force } = &cli.command { ... }` and use `*force`, or
- `match &cli.command { Commands::KillDaemon { force } => ... , _ => {} }`

## Fix Focus Areas
- src/lib.rs[873-980]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Kill-daemon prompt can hang ✓ Resolved 🐞 Bug ☼ Reliability ⭐ New
Description
kill-daemon decides “interactive” using only stdin.is_terminal(), but prints the confirmation
prompt to stderr; if stdin is a TTY while stderr is redirected, the command will block waiting
for input with no visible prompt. This makes the new safety guard appear to hang and can confuse
users in common redirection scenarios.
Code

src/lib.rs[R884-905]

+        use std::io::IsTerminal;
+        match kill_daemon_decision(*force, std::io::stdin().is_terminal()) {
+            KillDaemonDecision::RefuseNonInteractive => {
+                return Err(error::CliError::new(
+                    error::ErrorCode::InvalidInput,
+                    "Refusing to kill the daemon: this will NOT fix \"Failed to connect to \
+                     Chrome\" errors and will force the human to re-approve Chrome's \
+                     remote-debugging connection. If you are certain this is what you want, \
+                     re-run with --force.",
+                )
+                .into());
+            }
+            KillDaemonDecision::PromptUser => {
+                use std::io::Write;
+                eprint!(
+                    "Kill the daemon? This drops the approved Chrome connection and will \
+                     require re-approval in Chrome. [y/N] "
+                );
+                std::io::stderr().flush().ok();
+                let mut answer = String::new();
+                std::io::stdin().read_line(&mut answer).ok();
+                if !matches!(answer.trim().to_ascii_lowercase().as_str(), "y" | "yes") {
Evidence
The code checks only stdin.is_terminal() to choose PromptUser, then emits the prompt via
eprint! to stderr and reads from stdin; this combination can block without a visible prompt when
stderr is redirected.

src/lib.rs[878-908]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`kill-daemon` treats a session as interactive if `stdin` is a TTY, but the confirmation prompt is written to `stderr`. If `stderr` is not a TTY (e.g., `2>file`) while `stdin` is still a TTY, the program waits for input without the user seeing the prompt.

### Issue Context
The prompt/refusal policy is meant to prevent non-interactive callers (agents/scripts) from silently dropping an already-approved Chrome connection. The current terminal detection is incomplete for prompt visibility.

### Fix Focus Areas
- src/lib.rs[878-910]

### Suggested fix
- Compute an “interactive and can prompt” boolean using **both** stdin and stderr, e.g.:
 - `let can_prompt = std::io::stdin().is_terminal() && std::io::stderr().is_terminal();`
 - Pass `can_prompt` into `kill_daemon_decision(...)`.
- Treat cases where `stdin` is a TTY but `stderr` is not as **non-interactive** (refuse unless `--force`), to avoid blocking without a visible prompt.
- Add/adjust unit tests around `kill_daemon_decision` policy if you change its inputs (e.g., rename `is_tty` to `can_prompt`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Diff allocates unused node lists ✓ Resolved 🐞 Bug ➹ Performance
Description
compare_heapsnapshots_offline always computes full per-class diffs including
added_nodes/deleted_nodes, and the summary formatter does not use these vectors, so summary-only
runs still pay the memory cost. Additionally, diff_snapshots() eagerly
Vec::with_capacity(cur_agg.nodes.len())/Vec::with_capacity(base_len) for every class before
knowing whether the class changed, increasing peak memory on large snapshots.
Code

src/commands/memory.rs[R456-509]

+        // Upper-bounds: every current node could be new, every base node
+        // could be gone. Avoids reallocation churn on large classes.
+        let mut added_nodes: Vec<(u64, u64)> = Vec::with_capacity(cur_agg.nodes.len());
+        let base_len = base_agg.as_ref().map(|b| b.nodes.len()).unwrap_or(0);
+        let mut deleted_nodes: Vec<(u64, u64)> = Vec::with_capacity(base_len);
+        let mut added_size: u64 = 0;
+        let mut removed_size: u64 = 0;
+
+        if let Some(b) = &base_agg {
+            for (id, size) in &cur_agg.nodes {
+                if !b.nodes.contains_key(id) {
+                    added_nodes.push((*id, *size));
+                    added_size += size;
+                }
+            }
+            for (id, size) in &b.nodes {
+                if !cur_agg.nodes.contains_key(id) {
+                    deleted_nodes.push((*id, *size));
+                    removed_size += size;
+                }
+            }
+        } else {
+            for (id, size) in &cur_agg.nodes {
+                added_nodes.push((*id, *size));
+                added_size += size;
+            }
+        }
+
+        let added_count = added_nodes.len();
+        let removed_count = deleted_nodes.len();
+        if added_count > 0 || removed_count > 0 {
+            // Sort deterministically by node id so summary/detail indices
+            // stay stable across runs.
+            if added_count > 1 {
+                added_nodes.sort_unstable_by_key(|(id, _)| *id);
+            }
+            if removed_count > 1 {
+                deleted_nodes.sort_unstable_by_key(|(id, _)| *id);
+            }
+
+            let count_delta = added_count as i64 - removed_count as i64;
+            let size_delta = added_size as i64 - removed_size as i64;
+
+            diffs.push(HeapSnapshotClassDiff {
+                class_name: name,
+                added_count,
+                removed_count,
+                count_delta,
+                added_size,
+                removed_size,
+                size_delta,
+                added_nodes,
+                deleted_nodes,
+            });
Evidence
The diff data structure stores per-node vectors, diff_snapshots() eagerly allocates them, but the
summary formatter ignores them and compare_heapsnapshots_offline() still computes them even when
only summary output is requested.

src/commands/memory.rs[311-315]
src/commands/memory.rs[431-437]
src/commands/memory.rs[443-510]
src/commands/memory.rs[567-613]
src/commands/memory.rs[674-717]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The offline heapsnapshot diff path allocates and retains per-node ID vectors (`added_nodes` / `deleted_nodes`) even when the caller only requests the summary (no `--class-index`). This makes the summary-only mode unnecessarily memory-heavy. In addition, `diff_snapshots()` preallocates these vectors to the full class size for every class, even if the class ends up unchanged and filtered out.

## Issue Context
- `HeapSnapshotClassDiff` stores `added_nodes` / `deleted_nodes`.
- `format_class_diff_summary()` does not read those fields.
- `compare_heapsnapshots_offline()` always builds `diffs` (including node vectors) and then may format only the summary.

## Fix
Refactor so summary runs do not allocate/store per-id vectors:
- Introduce a lightweight summary struct (no per-id vectors) and compute that when `class_index.is_none()`.
- For `--class-index`, either:
 - compute summary first (no vectors), resolve the target class by index, then compute per-id detail only for that single class, OR
 - defer allocating `added_nodes`/`deleted_nodes` until a difference is detected and only store them when detail is actually requested.
- Avoid `Vec::with_capacity(cur_agg.nodes.len())` / `Vec::with_capacity(base_len)` up-front; allocate lazily (or reserve after first delta is found).

## Fix Focus Areas
- src/commands/memory.rs[311-419]
- src/commands/memory.rs[439-553]
- src/commands/memory.rs[566-717]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Extra ID set allocation ✓ Resolved 🐞 Bug ➹ Performance ⭐ New
Description
The session-mismatch heuristic builds a HashSet containing every base snapshot node ID to test
overlap, adding a full additional O(n) allocation even though IDs are already present in per-class
maps. On large snapshots this can add noticeable extra memory/time overhead before producing output.
Code

src/commands/memory.rs[R710-718]

+    let base_nodes_count: usize = base.values().map(|a| a.nodes.len()).sum();
+    let mut base_ids: std::collections::HashSet<u64> =
+        std::collections::HashSet::with_capacity(base_nodes_count);
+    base_ids.extend(base.values().flat_map(|a| a.nodes.keys().copied()));
+    !current
+        .values()
+        .flat_map(|a| a.nodes.keys())
+        .any(|id| base_ids.contains(id))
+}
Evidence
The heuristic explicitly sums all base node counts, allocates a HashSet with that capacity, inserts
every base ID, and is invoked during every offline compare before formatting output.

src/commands/memory.rs[694-718]
src/commands/memory.rs[720-769]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`snapshots_share_no_node_ids` allocates and fills a `HashSet<u64>` of all base IDs, then scans current IDs to detect any overlap. This duplicates ID storage and can add extra memory/time overhead for large snapshots.

### Issue Context
This is a best-effort warning heuristic. The compare path already builds per-class aggregates, so this extra allocation is purely for the warning and can be optimized.

### Fix Focus Areas
- src/commands/memory.rs[694-718]
- src/commands/memory.rs[720-769]

### Suggested fix
- Build the `HashSet` from the **smaller** of base/current (whichever has fewer total IDs), then scan the other side for membership.
- Alternatively, cap the heuristic work (e.g., sample first N IDs) since it is only used for a warning.
- Keep behavior identical (warning only) while reducing peak overhead.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit 0c9120b

Results up to commit 41f6c67


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Moved cli.command breaks build ✓ Resolved 🐞 Bug ≡ Correctness
Description
In src/lib.rs::run(), the kill-daemon early-intercept matches on cli.command by value, moving
the field out of cli. The function later matches on &cli.command for other commands, which will
not compile due to Rust’s partial-move rules.
Code

src/lib.rs[R874-876]

+    if let Commands::KillDaemon { force } = cli.command {
+        use std::io::IsTerminal;
+        match kill_daemon_decision(force, std::io::stdin().is_terminal()) {
Evidence
cli.command is moved in the kill-daemon branch, but later branches still borrow &cli.command,
which Rust forbids after a move.

src/lib.rs[873-875]
src/lib.rs[982-1012]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`run()` currently does `if let Commands::KillDaemon { force } = cli.command { ... }`, which moves `cli.command` out of `cli`. Later in the same function it still uses `&cli.command` (for offline intercepts like `inspect-heapsnapshot-node` / `compare-heapsnapshots`), which causes a compile-time partial-move error.

## Issue Context
This is a build-blocking ownership bug introduced by the new `KillDaemon { force: bool }` variant handling.

## Fix
Change the kill-daemon intercept to match by reference, e.g.:
- `if let Commands::KillDaemon { force } = &cli.command { ... }` and use `*force`, or
- `match &cli.command { Commands::KillDaemon { force } => ... , _ => {} }`

## Fix Focus Areas
- src/lib.rs[873-980]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
2. Diff allocates unused node lists ✓ Resolved 🐞 Bug ➹ Performance
Description
compare_heapsnapshots_offline always computes full per-class diffs including
added_nodes/deleted_nodes, and the summary formatter does not use these vectors, so summary-only
runs still pay the memory cost. Additionally, diff_snapshots() eagerly
Vec::with_capacity(cur_agg.nodes.len())/Vec::with_capacity(base_len) for every class before
knowing whether the class changed, increasing peak memory on large snapshots.
Code

src/commands/memory.rs[R456-509]

+        // Upper-bounds: every current node could be new, every base node
+        // could be gone. Avoids reallocation churn on large classes.
+        let mut added_nodes: Vec<(u64, u64)> = Vec::with_capacity(cur_agg.nodes.len());
+        let base_len = base_agg.as_ref().map(|b| b.nodes.len()).unwrap_or(0);
+        let mut deleted_nodes: Vec<(u64, u64)> = Vec::with_capacity(base_len);
+        let mut added_size: u64 = 0;
+        let mut removed_size: u64 = 0;
+
+        if let Some(b) = &base_agg {
+            for (id, size) in &cur_agg.nodes {
+                if !b.nodes.contains_key(id) {
+                    added_nodes.push((*id, *size));
+                    added_size += size;
+                }
+            }
+            for (id, size) in &b.nodes {
+                if !cur_agg.nodes.contains_key(id) {
+                    deleted_nodes.push((*id, *size));
+                    removed_size += size;
+                }
+            }
+        } else {
+            for (id, size) in &cur_agg.nodes {
+                added_nodes.push((*id, *size));
+                added_size += size;
+            }
+        }
+
+        let added_count = added_nodes.len();
+        let removed_count = deleted_nodes.len();
+        if added_count > 0 || removed_count > 0 {
+            // Sort deterministically by node id so summary/detail indices
+            // stay stable across runs.
+            if added_count > 1 {
+                added_nodes.sort_unstable_by_key(|(id, _)| *id);
+            }
+            if removed_count > 1 {
+                deleted_nodes.sort_unstable_by_key(|(id, _)| *id);
+            }
+
+            let count_delta = added_count as i64 - removed_count as i64;
+            let size_delta = added_size as i64 - removed_size as i64;
+
+            diffs.push(HeapSnapshotClassDiff {
+                class_name: name,
+                added_count,
+                removed_count,
+                count_delta,
+                added_size,
+                removed_size,
+                size_delta,
+                added_nodes,
+                deleted_nodes,
+            });
Evidence
The diff data structure stores per-node vectors, diff_snapshots() eagerly allocates them, but the
summary formatter ignores them and compare_heapsnapshots_offline() still computes them even when
only summary output is requested.

src/commands/memory.rs[311-315]
src/commands/memory.rs[431-437]
src/commands/memory.rs[443-510]
src/commands/memory.rs[567-613]
src/commands/memory.rs[674-717]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The offline heapsnapshot diff path allocates and retains per-node ID vectors (`added_nodes` / `deleted_nodes`) even when the caller only requests the summary (no `--class-index`). This makes the summary-only mode unnecessarily memory-heavy. In addition, `diff_snapshots()` preallocates these vectors to the full class size for every class, even if the class ends up unchanged and filtered out.

## Issue Context
- `HeapSnapshotClassDiff` stores `added_nodes` / `deleted_nodes`.
- `format_class_diff_summary()` does not read those fields.
- `compare_heapsnapshots_offline()` always builds `diffs` (including node vectors) and then may format only the summary.

## Fix
Refactor so summary runs do not allocate/store per-id vectors:
- Introduce a lightweight summary struct (no per-id vectors) and compute that when `class_index.is_none()`.
- For `--class-index`, either:
 - compute summary first (no vectors), resolve the target class by index, then compute per-id detail only for that single class, OR
 - defer allocating `added_nodes`/`deleted_nodes` until a difference is detected and only store them when detail is actually requested.
- Avoid `Vec::with_capacity(cur_agg.nodes.len())` / `Vec::with_capacity(base_len)` up-front; allocate lazily (or reserve after first delta is found).

## Fix Focus Areas
- src/commands/memory.rs[311-419]
- src/commands/memory.rs[439-553]
- src/commands/memory.rs[566-717]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

Comment thread src/lib.rs Outdated
Comment thread src/commands/memory.rs Outdated
add compare-heapsnapshots command and documentation for memory leak debugging workflow
refactor diffing to separate summary vs per-node detail computation (lazy detail resolution)
remove per-node vectors from summary structs to reduce memory usage
add compute_class_node_detail for on-demand node-level diff output
detect and warn on snapshots with zero shared node IDs (likely different sessions)
validate snapshot node array alignment to catch truncated/malformed files early
fix node iteration bounds to ensure full and consistent traversal
improve CSV/text formatting with consistent newline handling
make connect timeout parsing pure, validated, and unit-testable
add extensive unit tests for timeout parsing, snapshot validation, and diff correctness
update CLI and docs with constraints on same-session snapshot comparison
harden kill-daemon handling by avoiding partial moves in match logic
@aeroxy

aeroxy commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new offline command compare-heapsnapshots to compare two .heapsnapshot files and report per-class added/deleted nodes. It also updates the kill-daemon command with safety guards and confirmation prompts, refusing to run non-interactively without the --force flag, and adds a timeout to the Chrome WebSocket connection handshake to prevent indefinite hangs. The review feedback identifies a critical compilation error in src/commands/memory.rs due to the use of the non-standard is_multiple_of method on usize, and suggests performance optimizations for heap snapshot diffing, such as avoiding redundant Option checks inside loops and pre-allocating capacity for HashSet collections.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/commands/memory.rs
Comment thread src/commands/memory.rs Outdated
Comment thread src/commands/memory.rs Outdated
…etection

refactor compute_class_node_detail to avoid repeated Option checks inside loops
handle class-new and class-deleted cases with direct branches for efficiency
reduce per-iteration overhead with simplified HashMap lookups
preallocate HashSet capacity in snapshots_share_no_node_ids to avoid reallocation during ID collection
@aeroxy

aeroxy commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

replace explicit error check with ? operator for cleaner propagation
@aeroxy

aeroxy commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@aeroxy

aeroxy commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@aeroxy

aeroxy commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@aeroxy

aeroxy commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

/agentic_review

Comment thread src/lib.rs
Comment thread src/commands/memory.rs Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9ae27ee

@aeroxy

aeroxy commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new offline command compare-heapsnapshots to diff two V8 heap snapshots and group V8 heap objects by class or type name, accompanied by extensive documentation and tests. It also adds a connection timeout for Chrome WebSocket handshakes to prevent indefinite hangs, and updates the kill-daemon command to require a --force flag when run non-interactively. The review feedback correctly identifies two critical compilation errors in the new heap snapshot diffing code: the use of the non-standard is_multiple_of method on usize, and a type mismatch when attempting to add a reference &u64 to a u64 variable without dereferencing.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/commands/memory.rs
Comment thread src/commands/memory.rs
@aeroxy

aeroxy commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 0c9120b

@aeroxy aeroxy merged commit ed75374 into main Jul 11, 2026
1 check passed
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