Skip to content

Fix network commands in daemon mode#38

Merged
ractive merged 2 commits intomainfrom
iter-37/network-daemon-fix
Apr 8, 2026
Merged

Fix network commands in daemon mode#38
ractive merged 2 commits intomainfrom
iter-37/network-daemon-fix

Conversation

@ractive
Copy link
Copy Markdown
Owner

@ractive ractive commented Apr 8, 2026

Summary

  • Fix Performance API fallback timeout (network --limit in daemon mode): drain_daemon_events now handles interleaved Firefox push events that could arrive before the daemon's drain response. evaluate_js_async now skips consoleAPICall/pageError push events that share the console actor's from field, preventing misidentification as the eval acknowledgement.
  • Fix navigate event capture (navigate --with-network --network-timeout): New drain_network_events_timed() uses total elapsed wall-clock time instead of a per-read idle timeout. This captures network events that arrive in bursts during page navigation (e.g., after a 1-2s gap while the page loads). --network-timeout now means total collection time, not idle gap between events.
  • network --follow and network (snapshot mode) are unchanged — only navigate --with-network uses the new timed drain.

Test plan

  • cargo fmt — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace — all tests pass
  • Manual: ff-rdp network --limit 20 in daemon mode returns Performance API results
  • Manual: ff-rdp navigate --with-network --network-timeout 5000 <url> captures full page load
  • Manual: ff-rdp network --follow still streams events in real time

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • navigate --with-network --network-timeout now counts total elapsed time (captures events across short idle gaps) instead of stopping on idle reads
    • Network collection in daemon mode now robustly handles interleaved forwarded messages
    • JavaScript evaluation in daemon mode skips unsolicited console pushes to reliably match eval responses
  • Documentation

    • Added notes and a research doc describing the daemon-mode network and evaluation fixes

…capture

Two daemon-mode network bugs fixed:

1. Performance API fallback eval timeout (network --limit):
   - drain_daemon_events now handles interleaved Firefox messages (consoleAPICall
     push events) that could arrive before the daemon's drain response
   - evaluate_js_async now skips consoleAPICall/pageError push events that share
     the console actor's `from` field, preventing misidentification as eval ack

2. Navigate --with-network captures too few events:
   - New drain_network_events_timed() uses total elapsed time instead of per-read
     idle timeout, capturing events that arrive in bursts during page navigation
   - --network-timeout now means total collection time, not idle gap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae24b6bf-4623-4ba2-8a7d-e2b11c568af7

📥 Commits

Reviewing files that changed from the base of the PR and between 62203c7 and c790a6e.

📒 Files selected for processing (1)
  • crates/ff-rdp-cli/src/commands/network_events.rs

📝 Walkthrough

Walkthrough

Replaces per-read idle timeouts with a wall-clock total timeout for network-event draining, and adds message-filtering/looping to avoid consuming forwarded Firefox events before the daemon's responses; also updates help text and related calling sites to use the new timed drain.

Changes

Cohort / File(s) Summary
CLI Args
crates/ff-rdp-cli/src/cli/args.rs
Reworded --network-timeout help/semantics from an "idle timeout" to a "total time limit" (type/default unchanged).
Timed Network Drain
crates/ff-rdp-cli/src/commands/network_events.rs
Added drain_network_events_timed(transport, total_timeout) which uses a wall-clock deadline and short (500ms) poll intervals, ignoring per-read timeouts to collect events for the full duration.
Navigate Command
crates/ff-rdp-cli/src/commands/navigate.rs
Switched drain calls to use drain_network_events_timed(...) in both daemon and direct paths and removed prior per-read timeout overrides tied to the drain phase.
Daemon Client
crates/ff-rdp-cli/src/daemon/client.rs
Made drain_daemon_events loop (up to 64 frames), discarding non-daemon forwarded messages until a from: "daemon" response is seen and surface errors from the daemon response.
Console Actor
crates/ff-rdp-core/src/actors/console.rs
Refactored evaluate_js_async to send evaluateJSAsync directly and loop-read responses, explicitly skipping unsolicited push events (consoleAPICall, pageError) and mapping actor errors to ProtocolError::ActorError.
Docs / KB
kb/iterations/iteration-37-network-daemon-fix.md, kb/research/network-daemon-issues.md
Added iteration notes and a resolved research doc describing the daemon-mode issues, root causes (idle-timeouts and unfiltered forwarded messages), and the applied fixes and test/recording recommendations.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI/navigate
    participant Transport
    participant Drain as drain_network_events_timed
    participant Daemon as Daemon/Firefox

    CLI->>Daemon: navigateTo (request)
    Note over Daemon: Page loads → emits resource events (bursts)
    Daemon-->>Transport: resource-available / resource-updated messages

    CLI->>Drain: start drain(total_timeout)
    Drain->>Transport: set_read_timeout(500ms)
    loop while now < deadline
        Transport-->>Drain: message or Timeout
        alt message is daemon/resource
            Drain->>Drain: parse & accumulate
        else unsolicited/other
            Drain->>Drain: ignore / continue
        end
    end

    Drain-->>CLI: (Vec<NetworkResource>, Vec<NetworkResourceUpdate>)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped the bytes and chased a trace,

I timed the ticks across the race,
No idle gap shall steal the pile,
Daemon skips the stray console while,
Events returned — I thumped in place!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix network commands in daemon mode' accurately summarizes the main objective of the PR, which focuses on fixing two daemon-mode network bugs and timeout semantics.
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 iter-37/network-daemon-fix

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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes daemon-mode network command reliability by hardening message draining / response matching and revising navigate --with-network collection to be wall-clock bounded rather than idle-gap bounded.

Changes:

  • Make daemon drain and console eval handling robust to interleaved Firefox push events in daemon mode.
  • Add drain_network_events_timed() and switch navigate --with-network to use a total elapsed collection window.
  • Update CLI help text and add KB research/iteration writeups documenting the root causes and fixes.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
kb/research/network-daemon-issues.md Documents root causes and fixes for daemon-mode network issues.
kb/iterations/iteration-37-network-daemon-fix.md Iteration plan/results for the daemon-mode network fixes.
crates/ff-rdp-core/src/actors/console.rs Makes evaluate_js_async skip console push events so they aren’t misread as eval acks/results.
crates/ff-rdp-cli/src/daemon/client.rs Makes drain_daemon_events resilient to interleaved non-daemon frames.
crates/ff-rdp-cli/src/commands/network_events.rs Adds drain_network_events_timed() for total-time-bounded event collection.
crates/ff-rdp-cli/src/commands/navigate.rs Switches navigate --with-network to the timed drain approach.
crates/ff-rdp-cli/src/cli/args.rs Updates --network-timeout help text to reflect total-time semantics.
Comments suppressed due to low confidence (1)

crates/ff-rdp-cli/src/commands/navigate.rs:26

  • The restore_timeout doc comment still refers to being called after drain_network_events, but navigate --with-network now uses drain_network_events_timed. Updating this comment will keep the guidance accurate for future changes.
/// Restore the socket read timeout to the value established at connect time.
///
/// Called after `drain_network_events` completes so that subsequent RDP
/// round-trips (e.g. unwatch, wait condition polling) use the original timeout.
/// Failures are logged and swallowed — the drain has already completed.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +83 to +87
Err(ProtocolError::Timeout) => {
// Per-read timeout — check if total elapsed time has exceeded the limit.
if start.elapsed() >= total_timeout {
break;
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

drain_network_events_timed only checks start.elapsed() when transport.recv() returns ProtocolError::Timeout. If messages keep arriving more frequently than the 500ms poll interval, recv() will never time out and this loop can run well past total_timeout, contradicting the CLI help text/semantics. Consider enforcing the deadline on every iteration (or by dynamically setting the read timeout to the remaining time) so the function stops after the requested wall-clock duration even under continuous traffic.

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +94

// Set a short read timeout for responsive polling.
transport.set_read_timeout(Some(poll_interval))?;

let mut all_resources = Vec::new();
let mut all_updates = Vec::new();

loop {
match transport.recv() {
Ok(msg) => {
let msg_type = msg.get("type").and_then(Value::as_str).unwrap_or_default();
match msg_type {
"resources-available-array" => {
all_resources.extend(parse_network_resources(&msg));
}
"resources-updated-array" => {
all_updates.extend(parse_network_resource_updates(&msg));
}
_ => {}
}
}
Err(ProtocolError::Timeout) => {
// Per-read timeout — check if total elapsed time has exceeded the limit.
if start.elapsed() >= total_timeout {
break;
}
// Otherwise keep polling.
}
Err(e) => return Err(e),
}
}

Ok((all_resources, all_updates))
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

drain_network_events_timed changes the transport's read timeout (set_read_timeout(Some(poll_interval))) but does not restore the previous value. Right now callers restore manually, but this side effect is easy to miss and can break subsequent RDP round-trips if a future caller forgets. Consider saving the previous timeout and restoring it before returning (or documenting prominently that callers must restore it).

Suggested change
// Set a short read timeout for responsive polling.
transport.set_read_timeout(Some(poll_interval))?;
let mut all_resources = Vec::new();
let mut all_updates = Vec::new();
loop {
match transport.recv() {
Ok(msg) => {
let msg_type = msg.get("type").and_then(Value::as_str).unwrap_or_default();
match msg_type {
"resources-available-array" => {
all_resources.extend(parse_network_resources(&msg));
}
"resources-updated-array" => {
all_updates.extend(parse_network_resource_updates(&msg));
}
_ => {}
}
}
Err(ProtocolError::Timeout) => {
// Per-read timeout — check if total elapsed time has exceeded the limit.
if start.elapsed() >= total_timeout {
break;
}
// Otherwise keep polling.
}
Err(e) => return Err(e),
}
}
Ok((all_resources, all_updates))
let previous_read_timeout = transport.read_timeout()?;
// Set a short read timeout for responsive polling.
transport.set_read_timeout(Some(poll_interval))?;
let result = {
let mut all_resources = Vec::new();
let mut all_updates = Vec::new();
loop {
match transport.recv() {
Ok(msg) => {
let msg_type = msg.get("type").and_then(Value::as_str).unwrap_or_default();
match msg_type {
"resources-available-array" => {
all_resources.extend(parse_network_resources(&msg));
}
"resources-updated-array" => {
all_updates.extend(parse_network_resource_updates(&msg));
}
_ => {}
}
}
Err(ProtocolError::Timeout) => {
// Per-read timeout — check if total elapsed time has exceeded the limit.
if start.elapsed() >= total_timeout {
break;
}
// Otherwise keep polling.
}
Err(e) => break Err(e),
}
}
Ok((all_resources, all_updates))
};
transport.set_read_timeout(previous_read_timeout)?;
result

Copilot uses AI. Check for mistakes.
…raffic

Move the elapsed-time check to the top of the loop so the function stops
even when messages arrive faster than the 500ms poll interval, matching
the documented total-time-limit semantics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ractive ractive merged commit 476b1e9 into main Apr 8, 2026
4 of 5 checks passed
@ractive ractive deleted the iter-37/network-daemon-fix branch April 8, 2026 19:28
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.

2 participants