Skip to content

fix(recording): resolve PipeWire system audio routing timeout on Linux (#3514) - #3534

Open
devpryan7792 wants to merge 2 commits into
CapSoftware:mainfrom
devpryan7792:fix/issue-3514-linux-pipewire-audio-route-timeout
Open

devpryan7792 wants to merge 2 commits into
CapSoftware:mainfrom
devpryan7792:fix/issue-3514-linux-pipewire-audio-route-timeout

Conversation

@devpryan7792

@devpryan7792 devpryan7792 commented Oct 29, 2026 •

Copy link
Copy Markdown

Problem

On Linux installations running PipeWire / WirePlumber (such as Linux Mint, Arch, Ubuntu 24.04), system audio capture can fail during recording startup with:

studio recording actor: Linux system audio could not reach the output monitor: Linux system audio input 'pulse' failed to connect: BuildStream: Could not confirm the Linux audio input route: PulseAudio/PipeWire acknowledged routing stream <id> to '<device>.monitor', but the input did not change

Two distinct race conditions caused this failure:

  1. When the system audio stream was already connected to the output monitor (for example, if the monitor was designated as default source before launching Cap), move_pactl_source_output unconditionally issued pactl move-source-output without verifying whether the route was already satisfied.
  2. PipeWire processes stream node reassignment asynchronously over D-Bus and the WirePlumber policy graph. Cap polled for confirmation only 10 times at 20ms intervals (200ms total), timing out while WirePlumber was still linking the stream. Additionally, if the stream momentarily transitioned during node rebuild, source_output_route_matches returned an error that prematurely aborted the retry loop.

Fix

  • Fast-path route check: move_pactl_source_output now checks if the stream output is already mapped to the desired source_index. If already matched, it returns Ok(()) immediately without issuing redundant move commands or causing WirePlumber state churn.
  • Idempotency fallback: If pactl move-source-output returns non-zero, it re-verifies if the stream is already routed to the destination before failing.
  • Asynchronous link polling: Extended the route confirmation polling from 200ms to a robust 2.0-second deadline with 50ms intervals, tolerating transient transition states during WirePlumber node renegotiation.
  • Diagnostic clarity: Retained clear diagnostic error reporting if the 2-second timeout is exceeded.

Testing

  • Added unit test already_matching_source_output_is_identified in crates/recording/src/sources/screen_capture/linux.rs verifying that matching source outputs are detected accurately.
  • Verified existing routing_acknowledgement_requires_the_requested_source and pactl helper unit tests.
  • Verified formatting with cargo fmt --check.

Closes #3514

RetriggerView in GreptileConfidence Score: 4/5

Fix the denied nested conditions and the comment-policy violations before merging.

Findings

  1. P1 Nested conditions fail Clippy ▶
  2. P2 Comments repeat the code ▶
  3. P2 Test misses the early return ▶
Fix with agent prompt
### Issue 1
crates/recording/src/sources/screen_capture/linux.rs:2256-2260
The new nested `if` checks violate the repository’s instruction to combine conditions and its denied `collapsible_if` lint. Linux Clippy checks will reject this code. Combine the conditions with let chains here, in the failed-command fallback, and in the polling loop.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 2
crates/recording/src/sources/screen_capture/linux.rs:2255
The new comments at lines 2255 and 2299 only narrate the code. `AGENTS.md` explicitly bans comments that restate what the code does. Remove these two comments before merging to meet that requirement. Keep the PipeWire timing explanation, which gives useful platform context.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 3
crates/recording/src/sources/screen_capture/linux.rs:3451-3453
`already_matching_source_output_is_identified` tests a copied expression rather than production code. It still passes if the new early return is deleted or its condition is broken. Test the routing function with an injected command runner so the test proves that an already-matching stream returns successfully without running `move-source-output`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This PR avoids redundant Linux audio moves and gives PipeWire more time to confirm a route.

  • Linux system audio skips settled moves and waits for monitor routing.

Reviews (1) · Last reviewed commit: "fix(recording): resolve PipeWire system ..." · Reviewed by Greptile

@vercel

vercel Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

@devpryan7792 is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

Comment on lines +2256 to +2260
if let Ok(outputs) = current_process_source_outputs() {
if outputs
.iter()
.any(|output| output.id == source_output && output.source == source_index)
{

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.

P1 Nested conditions fail Clippy

The new nested if checks violate the repository’s instruction to combine conditions and its denied collapsible_if lint. Linux Clippy checks will reject this code. Combine the conditions with let chains here, in the failed-command fallback, and in the polling loop.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/recording/src/sources/screen_capture/linux.rs
Line: 2256-2260

Comment:
**Nested conditions fail Clippy**

The new nested `if` checks violate the repository’s instruction to combine conditions and its denied `collapsible_if` lint. Linux Clippy checks will reject this code. Combine the conditions with let chains here, in the failed-command fallback, and in the polling loop.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

)?
}
};
// Fast path: if the stream is already routed to the requested source, return immediately.

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.

P2 Comments repeat the code

The new comments at lines 2255 and 2299 only narrate the code. AGENTS.md explicitly bans comments that restate what the code does. Remove these two comments before merging to meet that requirement. Keep the PipeWire timing explanation, which gives useful platform context.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/recording/src/sources/screen_capture/linux.rs
Line: 2255

Comment:
**Comments repeat the code**

The new comments at lines 2255 and 2299 only narrate the code. `AGENTS.md` explicitly bans comments that restate what the code does. Remove these two comments before merging to meet that requirement. Keep the PipeWire timing explanation, which gives useful platform context.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +3451 to +3453
assert!(outputs.iter().any(|o| o.id == 1128 && o.source == 47));
assert!(!outputs.iter().any(|o| o.id == 1128 && o.source == 61));
assert!(!outputs.iter().any(|o| o.id == 1130 && o.source == 47));

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.

P2 Test misses the early return

already_matching_source_output_is_identified tests a copied expression rather than production code. It still passes if the new early return is deleted or its condition is broken. Test the routing function with an injected command runner so the test proves that an already-matching stream returns successfully without running move-source-output.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/recording/src/sources/screen_capture/linux.rs
Line: 3451-3453

Comment:
**Test misses the early return**

`already_matching_source_output_is_identified` tests a copied expression rather than production code. It still passes if the new early return is deleted or its condition is broken. Test the routing function with an injected command runner so the test proves that an already-matching stream returns successfully without running `move-source-output`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

- Use is_ok_and to eliminate nested conditions and comply with collapsible_if clippy rules
- Remove narrative comments per AGENTS.md style guide while preserving PipeWire timing documentation
- Update already_matching_source_output_is_identified test to directly verify source_output_route_matches

Signed-off-by: Pradyumn jha <[email protected]>

This branch has not been deployed

No deployments
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.

Linux: system audio fails, "Could not confirm the Linux audio input route ... but the input did not change" (PipeWire 1.0.5)

1 participant