Skip to content

Devana fixes#17

Merged
bnomei merged 16 commits into
mainfrom
devana-fixes
Jun 28, 2026
Merged

Devana fixes#17
bnomei merged 16 commits into
mainfrom
devana-fixes

Conversation

@bnomei

@bnomei bnomei commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added runtime enabling/disabling for muninn:// URL handling.
    • Improved external recording control behavior when recording is enabled/disabled.
  • Bug Fixes

    • Fixed toggle so it only starts recording when recording is enabled (otherwise it only stops an active recording).
    • Prevented whitespace-only text from triggering injection/replacements.
    • When accessibility is granted during the permission flow, injection proceeds immediately.
  • Improvements

    • Refined streaming transcription/replay finalization for reliability and safer replay (refine prompt redaction).
    • Improved Google STT transcript parsing by combining segments correctly.

bnomei added 15 commits June 26, 2026 19:31
The embedded Apple Speech helper path was cached in a OnceLock after first
materialization, so later transcriptions returned the cached path without
re-running helper_needs_refresh(). A same-user attacker could replace the
on-disk helper at its predictable temp path and have Muninn execute the
tampered binary on the next dictation.

Remove the OnceLock cache and re-validate the on-disk bytes against the
embedded helper on every call, rewriting atomically when they differ. The
HELPER_INIT mutex still serializes concurrent refreshes.

Ref: .devana/...-P0-apple-speech-helper-toctou.md
…ty grant (P1)

should_abort_injection aborted whenever Accessibility was requested during the
interaction, even when the refreshed preflight already reported it granted. That
returned Ok(()) without injecting, then process_and_inject deleted the temp WAV —
discarding a fully transcribed utterance with no retry path, forcing re-dictation.

Since the refreshed preflight confirms AXIsProcessTrusted is now true, injection
will succeed, so proceed instead of aborting. Genuine denials still abort.
Update the encoding test accordingly and add a still-denied abort case.

Ref: .devana/...-P1-accessibility-grant-loses-transcript.md
Replay persistence is enqueued to a background worker via try_send, but the
runtime deleted the temp WAV synchronously right after injection. When
replay_retain_audio was enabled the worker frequently found the source file
already gone and silently skipped audio retention, defeating full-debug replay.

Transfer WAV ownership to the worker when the request is accepted: enqueue now
returns whether the worker took ownership, the worker deletes the WAV only after
persist (and thus after audio retention), and the runtime deletes it itself only
when the request was not enqueued (replay disabled, queue full, or disconnected).
The service's Drop already joins the worker, so shutdown still persists+cleans up.

Ref: .devana/...-P1-replay-audio-deleted-before-persist.md
README documents that full-debug replay redacts prompt fields, and the config and
envelope snapshots already strip system_prompt, but replay_refine_context wrote
the verbatim transcript.system_prompt into record.json under
refine_context.system_prompt. That leaked private vocabulary, names, and project
hints into replay directories that may be shared or backed up.

Store the [redacted] marker instead of the prompt text, keeping the field present
so the artifact still records that a refine prompt was configured. Update the
regression test to assert redaction.

Ref: .devana/...-P1-replay-prompt-leak-refine-context.md
…njection (P2)

non_empty_text (orchestrator + scoring) and inject_checked only rejected
zero-length strings, so a whitespace-only output.final_text was preferred over a
usable transcript.raw_text and typed as blank output into the focused app. STT
and refine paths already use trim-based emptiness, so behavior was inconsistent.

Align all three with trim-based emptiness: whitespace-only text counts as absent
for routing/scoring (falling back to raw_text) and inject_checked rejects it. Add
orchestrator regression tests for whitespace fallback and no-injection.

Ref: .devana/...-P2-whitespace-only-text-injected.md
…cording_enabled (P2)

The README implied external 'toggle' starts recording when idle without the
start_recording_enabled opt-in, but the code (correctly) gates it like 'start'.
Leaving toggle ungated would let any external agent bypass the microphone opt-in
by sending 'toggle' instead of 'start', defeating the flag's security purpose, so
the gate is intentional and the docs were wrong.

Update the README and the action.rs doc comments to state that starting capture
from idle is gated for both Start and Toggle (the tray path forces the gate on
because a local human action is trusted). Add a regression assertion that an idle
external toggle is Disabled without the opt-in.

Ref: .devana/...-P2-external-toggle-start-gate.md
…nch (P2)

The muninn:// Apple Event handler was installed once at bootstrap when
url_scheme_enabled was true, and dispatch forwarded every parsed verb without
rechecking the flag. Disabling url_scheme_enabled via live config reload mutated
in-memory config but left the handler active, so stop/toggle/cancel URLs from any
local app still drove the runtime after an operator believed they were disabled.

Gate URL dispatch on a runtime AtomicBool seeded from the launch config and
refreshed on every ConfigReloaded event, and always install the handler so the
flag is a real bidirectional runtime control surface. When disabled, parsed URLs
are dropped with a log instead of reaching the worker.

Ref: .devana/...-P2-url-scheme-disable-ignored-reload.md
…st the first (P2)

finish() broke out of the post-commit read loop as soon as the first completed
transcription segment arrived, so multi-segment/multi-part transcripts were
truncated to the first segment and an empty-then-real completion sequence dropped
the real text. The accumulator already joins all completed entries, but finish()
never consumed the rest of the stream.

Drain segments instead: wait (bounded by the upstream finish timeout) for the
first completion, then briefly drain additional segments within a short grace
before finalizing. Reading until socket close is not viable here because the
realtime session is persistent and the upstream finish timeout would abort and
discard the transcript. Add regression tests for multi-segment accumulation and
empty-first-completion recovery.

Ref: .devana/...-P2-openai-streaming-truncates-segments.md
…ently (P2)

run_command wrote the full stdin payload and awaited shutdown before spawning the
stdout/stderr drains and before arming timeout(timeout_budget, child.wait()). A
child that emits output while reading (fills its stdout pipe and stops reading
stdin) or that never reads stdin to EOF blocked the parent's write_all forever,
and the timeout safety net was never reached — freezing the dictation pipeline.

Spawn the stdout/stderr readers before writing stdin so the child stays unblocked,
and drive the write/shutdown together with child.wait() under one timeout_budget
(WritePhaseError maps the failing phase to its CommandErrorKind). Add regression
tests for the large-stdin round-trip and the never-reads-stdin timeout. Mark the
devana report fixed.

Ref: .devana/...-P2-external-step-stdin-write-deadlock.md
… first (P2)

extract_google_transcript_text flattened results[]->alternatives[]->transcript and
took .next(), returning only the first segment's first alternative. Google's
speech:recognize returns one results entry per consecutive audio segment for
longer audio, so longer dictations were silently truncated to roughly the first
segment with no error.

Aggregate over all results, taking each result's first alternative and
concatenating them (segments carry their own leading spaces), then trim — matching
the Whisper and Deepgram adapters. Add regression tests for multi-segment
concatenation and first-alternative selection. Mark the devana report fixed.

Ref: .devana/...-P2-google-batch-multisegment-truncation.md
…ures (P3)

Both the Deepgram and OpenAI streaming backends finalized with
'Message::Close(_) => break' and then returned into_outcome(), which builds an
empty *success* outcome when no final text was seen. A server-initiated error
close (auth rejected, quota/credits exhausted, policy/internal fault) was
therefore reported to the user as 'no speech detected' instead of the real error,
hiding actionable failures like an expired API key.

Inspect the close frame: a non-benign code (not 1000 Normal / 1001 Going Away,
and a frame is present) is captured as StreamingTranscriptionError::failed with
the code and reason. The error is returned only when no usable final text was
produced, so a transcript completed before a trailing error close still succeeds.
Add regression tests for both backends. Mark the devana report fixed.

Ref: .devana/...-P3-streaming-error-close-as-empty.md
The P0/P1/P2 reports whose fixes did not modify the report file itself; the three
reports with an Agent Handoff section were committed alongside their fixes with
status updated to fixed.
@bnomei bnomei self-assigned this Jun 26, 2026
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds documentation across runtime, config, and support modules, and changes several control flows: external-control URL handling and start-recording gating, accessibility and whitespace handling in injection, replay enqueue/cleanup ownership, runner subprocess cleanup, and streaming transcription finalization.

Changes

Runtime and pipeline updates

Layer / File(s) Summary
External control enablement
README.md, src/external_control/*, src/runtime_shell.rs
ExternalControlOutcome now includes Noop, URL-scheme handling is gated by a runtime flag, MCP start-recording state uses shared atomic storage, and AppRuntime sets both runtime flags on startup and config reload.
Permission and injection gating
src/state.rs, src/runtime_flow.rs, src/error.rs, src/permissions.rs, src/runtime_permissions.rs, src/lib.rs, src/orchestrator.rs, src/scoring.rs, src/main.rs
Recording and injection state docs were expanded, permission refresh now proceeds after a successful accessibility prompt, whitespace-only text is treated as empty in injection and replacement paths, and tests reflect the updated gating.
Capture and runtime support docs
src/audio.rs, src/audio/render.rs, src/autostart.rs, src/injector.rs, src/platform.rs, src/target_context.rs, src/secrets.rs, src/hotkeys.rs, src/runtime_tray.rs, src/runtime_worker.rs, src/config_watch.rs
Audio capture/rendering, autostart, hotkeys, tray, platform, target-context, secrets, config-watcher, worker, and entry-point docs were added across the runtime support modules.
Replay persistence and cleanup
src/replay.rs, src/replay_dispatch.rs, src/runtime_pipeline.rs
Replay persistence now returns a success flag from enqueue, persists through a helper worker, cleans temporary WAVs only when ownership stays with the caller, and redacts refine_context.system_prompt before writing replay records.
Runner and builtin tool execution
src/runner.rs, src/runner/codec.rs, src/runner/execution.rs, src/runner/transport.rs, src/internal_tools.rs, src/refine.rs, src/stt_*_tool.rs, src/stt_apple_speech_tool.rs
The runner, transport, and builtin-step wrappers now document subprocess execution and add cleanup, timeout, helper-materialization, and transcript-extraction changes.
Transcription contract metadata
src/transcription.rs, src/streaming_transcription.rs
Transcription routes and attempts are written into envelope.extra["transcription"], and the streaming transcription contract documents session/provider lifecycles and outcome mapping.
Streaming provider finalization
src/streaming_transcription/*
Deepgram and OpenAI session finalization now handles close-frame errors and committed-item completion, while the Google adapter docs were updated.
Config and diagnostics docs
src/config.rs, src/config/logging.rs, src/envelope.rs, src/logging.rs, src/mock.rs, src/lib.rs
Configuration schemas, envelope builders, logging diagnostics, mock adapters, and tracing target constants gained Rustdoc and related serialization annotations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • bnomei/muninn#4: Shares the Whisper.cpp tool entrypoints and builtin-step plumbing touched again in src/stt_whisper_cpp_tool.rs.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is vague and does not describe the main changes in the pull request. Use a concise, specific title that summarizes the primary change, such as the major feature or behavior being updated.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

@augmentcode

augmentcode Bot commented Jun 26, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: This PR is a “Devana fixes” pass that tightens macOS runtime behavior and greatly expands inline documentation across Muninn.

Changes:

  • Clarifies external-control semantics: `toggle` start is gated by `start_recording_enabled` (matching `start`).
  • Adds extensive module/rustdoc across audio, config, runtime, pipeline, and provider code.
  • Adds a runtime gate for handling incoming muninn:// URLs and updates it on config reload.
  • Adjusts injection permission flow to proceed after an Accessibility prompt is granted; adds/updates tests.
  • Makes replay persistence async: enqueue returns acceptance status and the worker owns temp WAV cleanup when queued.
  • Improves pipeline subprocess transport to drain stdout/stderr while writing stdin; adds deadlock/timeout regression tests.
  • Improves streaming transcription: detect error close frames (Deepgram/OpenAI) and refine OpenAI segment accumulation.
  • Enhances Google STT JSON extraction to combine multi-result segments; adds tests.

Technical Notes: Several helpers now treat whitespace-only values as empty (e.g., injection routing and secret resolution) to avoid propagating blank output.

🤖 Was this summary useful? React with 👍 or 👎

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

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread src/stt_google_tool.rs Outdated
.and_then(|alternative| alternative.get("transcript"))
.and_then(Value::as_str)
})
.collect::<String>();

@augmentcode augmentcode Bot Jun 26, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/stt_google_tool.rs:622 — collect::<String>() concatenates per-result transcripts without inserting separators, so multi-result responses that don't include leading/trailing whitespace can produce merged words (e.g., "foo" + "bar" -> "foobar"). Consider verifying this against real Google results[] payloads where segments often don't carry spacing.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/replay_dispatch.rs Outdated
/// Queue replay persistence for one utterance.
///
/// Returns `true` when the worker accepted the request and will delete the
/// temporary WAV after writing. Returns `false` when replay is disabled, the

@augmentcode augmentcode Bot Jun 26, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/replay_dispatch.rs:57 — The docstring says enqueue returns false when replay is disabled, but the implementation doesn't check logging.replay_enabled and can still return true if the request was accepted into the queue. This could mislead callers that use the boolean to decide who owns WAV cleanup.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/runtime_permissions.rs Outdated
//! macOS TCC permission refresh before recording and injection.
//!
//! Hotkey recording requires Input Monitoring; tray and external controls use a
//! lighter preflight. Prompting during a user gesture may grant access but still

@augmentcode augmentcode Bot Jun 26, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/runtime_permissions.rs:4 — The module docs say a permission prompt during a user gesture “still aborts that gesture”, but should_abort_injection now proceeds when Accessibility was granted during the prompt. It may be worth narrowing this doc to recording-start behavior (or updating it to reflect injection’s new behavior).

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lib.rs (1)

414-443: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make temporary indicator updates a required capability.

These new defaults only forward to set_state, so any adapter that does not override them will latch Cancelled/Output instead of reverting. src/runtime_flow.rs and src/runtime_pipeline.rs already call these helpers as temporary transitions, so this weakens the cross-file contract in a user-visible way.

Suggested fix
 #[async_trait]
 pub trait IndicatorAdapter: Send + Sync {
@@
-    async fn set_temporary_state(
-        &mut self,
-        state: IndicatorState,
-        min_duration: Duration,
-        fallback_state: IndicatorState,
-    ) -> MacosAdapterResult<()> {
-        let _ = min_duration;
-        let _ = fallback_state;
-        self.set_state(state).await
-    }
+    async fn set_temporary_state(
+        &mut self,
+        state: IndicatorState,
+        min_duration: Duration,
+        fallback_state: IndicatorState,
+    ) -> MacosAdapterResult<()>;
@@
-    async fn set_temporary_state_with_glyph(
-        &mut self,
-        state: IndicatorState,
-        glyph: Option<char>,
-        min_duration: Duration,
-        fallback_state: IndicatorState,
-        fallback_glyph: Option<char>,
-    ) -> MacosAdapterResult<()> {
-        let _ = glyph;
-        let _ = fallback_glyph;
-        self.set_temporary_state(state, min_duration, fallback_state)
-            .await
-    }
+    async fn set_temporary_state_with_glyph(
+        &mut self,
+        state: IndicatorState,
+        glyph: Option<char>,
+        min_duration: Duration,
+        fallback_state: IndicatorState,
+        fallback_glyph: Option<char>,
+    ) -> MacosAdapterResult<()>;
 }
🤖 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 414 - 443, Make temporary indicator updates a
required adapter capability instead of a no-op default. Update the
IndicatorAdapter trait methods set_temporary_state and
set_temporary_state_with_glyph in src/lib.rs so they are no longer silently
implemented by delegating to set_state; either make them required to override or
provide behavior that truly reverts after min_duration. Ensure the runtime call
sites in runtime_flow and runtime_pipeline still rely on these methods as
temporary transitions, and adjust any concrete adapter implementations
accordingly.
src/runner/transport.rs (1)

142-180: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make timeout cleanup bounded too.

This branch times out write_and_wait, but then immediately awaits cleanup_after_command_failure, which drains stdout/stderr with no deadline. If a timed-out step leaves a descendant holding either pipe open, run_command can still block indefinitely and PipelineRunner::run no longer honors the step/global timeout budget.

🤖 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/runner/transport.rs` around lines 142 - 180, The timeout handling in
`run_command` only bounds `write_and_wait`, but the `Err(_)` timeout branch
still awaits `cleanup_after_command_failure` without any deadline, so the
command can block indefinitely while draining pipes. Update the timeout path in
`src/runner/transport.rs` so cleanup is also bounded by the remaining timeout
budget, using the same `timeout_budget`/deadline logic around
`cleanup_after_command_failure` and preserving the existing `CommandError`
construction in `run_command`.
🧹 Nitpick comments (1)
src/replay_dispatch.rs (1)

54-102: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for the WAV ownership contract.

enqueue() now decides whether src/runtime_pipeline.rs deletes the temp recording locally or leaves it to the worker. A focused test for accepted, full, and disconnected queue outcomes would make leaks and double-deletes much easier to catch on this cross-file path.

🤖 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/replay_dispatch.rs` around lines 54 - 102, Add a regression test around
ReplayDispatch::enqueue to verify the WAV ownership contract across the
src/runtime_pipeline.rs handoff. Cover the accepted, queue-full, and
disconnected paths so the test asserts when enqueue returns true the worker owns
cleanup, and when it returns false the caller still deletes the temp recording
locally. Use the enqueue method and ReplayPersistRequest/Sender try_send
behavior to locate the logic under test.
🤖 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/error.rs`:
- Around line 50-52: The MissingPermissions error variant is still formatting
permission names with debug output instead of the stable labels used elsewhere.
Update the Display implementation for MissingPermissions in error.rs so it
renders each PermissionKind via as_str() rather than using {:?}, keeping %error
logs consistent with the snake-case permission identifiers introduced in this
file.

In `@src/runner.rs`:
- Around line 337-338: Update the rustdoc on exit_status in RunnerTrace to match
the broader trace contract: it is not only for external child processes, since
run_in_process_step also sets Some(0) for successful builtin steps and
propagates InProcessStepError.exit_status on in-process failures. Adjust the
comment to describe exit_status as the step exit code when available, with None
only for timeout, and keep the wording aligned with run_in_process_step and
InProcessStepError.

In `@src/runtime_permissions.rs`:
- Around line 1-6: Update the module docs in runtime_permissions to match the
current permission flow: the top comment should no longer say prompt-driven
gestures always abort, since should_abort_injection() now continues when
Accessibility is granted in the same interaction. Also revise the comments
around should_abort_injection() and the Input Monitoring preflight so they only
describe Input Monitoring for RecordingStartSource::Hotkey, not tray/external
controls.

In `@src/runtime_shell.rs`:
- Around line 238-241: The MCP start_recording gating is still using the
startup-captured state after config reload, so update the server path to reflect
reloaded config. In runtime_shell::handle_config_reload (or the nearby reload
handling that calls set_url_scheme_enabled), make sure the MCP server state is
refreshed too—either by updating the stored start_recording_enabled flag or
recreating the MCP server instance—so both external_control and MCP honor the
new config consistently.

In `@src/stt_google_tool.rs`:
- Around line 614-623: The transcript assembly in the result-processing chain is
concatenating per-result segments directly, causing adjacent phrases to merge
without spaces. Update the logic around the filter_map/collect flow that builds
the transcript so it gathers each normalized segment separately and joins them
with a space instead of collecting into a single String. Keep the fix localized
to the transcript-building code in stt_google_tool.rs, near the `transcript`
construction and `raw_text` assignment.

---

Outside diff comments:
In `@src/lib.rs`:
- Around line 414-443: Make temporary indicator updates a required adapter
capability instead of a no-op default. Update the IndicatorAdapter trait methods
set_temporary_state and set_temporary_state_with_glyph in src/lib.rs so they are
no longer silently implemented by delegating to set_state; either make them
required to override or provide behavior that truly reverts after min_duration.
Ensure the runtime call sites in runtime_flow and runtime_pipeline still rely on
these methods as temporary transitions, and adjust any concrete adapter
implementations accordingly.

In `@src/runner/transport.rs`:
- Around line 142-180: The timeout handling in `run_command` only bounds
`write_and_wait`, but the `Err(_)` timeout branch still awaits
`cleanup_after_command_failure` without any deadline, so the command can block
indefinitely while draining pipes. Update the timeout path in
`src/runner/transport.rs` so cleanup is also bounded by the remaining timeout
budget, using the same `timeout_budget`/deadline logic around
`cleanup_after_command_failure` and preserving the existing `CommandError`
construction in `run_command`.

---

Nitpick comments:
In `@src/replay_dispatch.rs`:
- Around line 54-102: Add a regression test around ReplayDispatch::enqueue to
verify the WAV ownership contract across the src/runtime_pipeline.rs handoff.
Cover the accepted, queue-full, and disconnected paths so the test asserts when
enqueue returns true the worker owns cleanup, and when it returns false the
caller still deletes the temp recording locally. Use the enqueue method and
ReplayPersistRequest/Sender try_send behavior to locate the logic under test.
🪄 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

Run ID: 30259651-e2ff-4f90-b817-42b8c3292c32

📥 Commits

Reviewing files that changed from the base of the PR and between cf70bf2 and 3d703e4.

📒 Files selected for processing (51)
  • README.md
  • src/audio.rs
  • src/audio/render.rs
  • src/autostart.rs
  • src/config.rs
  • src/config/logging.rs
  • src/config_watch.rs
  • src/envelope.rs
  • src/error.rs
  • src/external_control.rs
  • src/external_control/action.rs
  • src/external_control/mcp.rs
  • src/external_control/status.rs
  • src/external_control/url_scheme.rs
  • src/hotkeys.rs
  • src/injector.rs
  • src/internal_tools.rs
  • src/lib.rs
  • src/logging.rs
  • src/main.rs
  • src/mock.rs
  • src/orchestrator.rs
  • src/permissions.rs
  • src/platform.rs
  • src/refine.rs
  • src/replay.rs
  • src/replay_dispatch.rs
  • src/runner.rs
  • src/runner/codec.rs
  • src/runner/execution.rs
  • src/runner/transport.rs
  • src/runtime_flow.rs
  • src/runtime_permissions.rs
  • src/runtime_pipeline.rs
  • src/runtime_shell.rs
  • src/runtime_tray.rs
  • src/runtime_worker.rs
  • src/scoring.rs
  • src/secrets.rs
  • src/state.rs
  • src/streaming_transcription.rs
  • src/streaming_transcription/deepgram.rs
  • src/streaming_transcription/google.rs
  • src/streaming_transcription/openai.rs
  • src/stt_apple_speech_tool.rs
  • src/stt_deepgram_tool.rs
  • src/stt_google_tool.rs
  • src/stt_openai_tool.rs
  • src/stt_whisper_cpp_tool.rs
  • src/target_context.rs
  • src/transcription.rs

Comment thread src/error.rs
Comment thread src/runner.rs Outdated
Comment thread src/runtime_permissions.rs
Comment thread src/runtime_shell.rs
Comment thread src/stt_google_tool.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.

Actionable comments posted: 1

🤖 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/runner/transport.rs`:
- Around line 316-324: In the bounded cleanup path in
drain_reader_until_cleanup_deadline usage, the current ordering drains stdout
before stderr even though only stderr is returned, which can waste the remaining
deadline if stdout stays open. Update the cleanup sequence in the transport
runner so stderr is drained first and its output/notes are preserved, then drain
stdout as best-effort discarded cleanup, keeping the existing use of
drain_reader_until_cleanup_deadline and the final stderr.output return behavior
intact.
🪄 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

Run ID: 0a7059ef-cf37-4865-abb7-bf081a5f55f9

📥 Commits

Reviewing files that changed from the base of the PR and between 3d703e4 and e5d47bc.

📒 Files selected for processing (19)
  • src/config/logging.rs
  • src/config_watch.rs
  • src/envelope.rs
  • src/error.rs
  • src/external_control/mcp.rs
  • src/lib.rs
  • src/logging.rs
  • src/mock.rs
  • src/orchestrator.rs
  • src/platform.rs
  • src/replay_dispatch.rs
  • src/runner.rs
  • src/runner/transport.rs
  • src/runtime_permissions.rs
  • src/runtime_shell.rs
  • src/secrets.rs
  • src/state.rs
  • src/stt_google_tool.rs
  • src/target_context.rs
✅ Files skipped from review due to trivial changes (9)
  • src/target_context.rs
  • src/platform.rs
  • src/state.rs
  • src/config/logging.rs
  • src/envelope.rs
  • src/config_watch.rs
  • src/secrets.rs
  • src/logging.rs
  • src/mock.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/runtime_permissions.rs
  • src/runner.rs
  • src/error.rs
  • src/orchestrator.rs
  • src/stt_google_tool.rs

Comment thread src/runner/transport.rs
Comment on lines +316 to +324
let stdout_drain =
drain_reader_until_cleanup_deadline(&mut stdout_reader, cleanup_deadline, "stdout").await;
cleanup_notes.extend(stdout_drain.notes);

let stderr = match drain_reader(stderr_reader).await {
Ok(stderr) => stderr,
Err(source) => {
cleanup_notes.push(format!("stderr drain during cleanup failed: {source}"));
CapturedOutput::default()
}
let stderr =
drain_reader_until_cleanup_deadline(&mut stderr_reader, cleanup_deadline, "stderr").await;
cleanup_notes.extend(stderr.notes);

(stderr.output.unwrap_or_default(), cleanup_notes)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Drain stderr before discarded stdout during bounded cleanup.

Line 316 waits on stdout first, but this function only returns stderr. If a descendant keeps stdout open until the deadline, stderr gets no remaining budget and useful failure output is lost.

Proposed fix
-    let stdout_drain =
-        drain_reader_until_cleanup_deadline(&mut stdout_reader, cleanup_deadline, "stdout").await;
-    cleanup_notes.extend(stdout_drain.notes);
-
     let stderr =
         drain_reader_until_cleanup_deadline(&mut stderr_reader, cleanup_deadline, "stderr").await;
     cleanup_notes.extend(stderr.notes);
+
+    let stdout_drain =
+        drain_reader_until_cleanup_deadline(&mut stdout_reader, cleanup_deadline, "stdout").await;
+    cleanup_notes.extend(stdout_drain.notes);
 
     (stderr.output.unwrap_or_default(), cleanup_notes)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let stdout_drain =
drain_reader_until_cleanup_deadline(&mut stdout_reader, cleanup_deadline, "stdout").await;
cleanup_notes.extend(stdout_drain.notes);
let stderr = match drain_reader(stderr_reader).await {
Ok(stderr) => stderr,
Err(source) => {
cleanup_notes.push(format!("stderr drain during cleanup failed: {source}"));
CapturedOutput::default()
}
let stderr =
drain_reader_until_cleanup_deadline(&mut stderr_reader, cleanup_deadline, "stderr").await;
cleanup_notes.extend(stderr.notes);
(stderr.output.unwrap_or_default(), cleanup_notes)
let stderr =
drain_reader_until_cleanup_deadline(&mut stderr_reader, cleanup_deadline, "stderr").await;
cleanup_notes.extend(stderr.notes);
let stdout_drain =
drain_reader_until_cleanup_deadline(&mut stdout_reader, cleanup_deadline, "stdout").await;
cleanup_notes.extend(stdout_drain.notes);
(stderr.output.unwrap_or_default(), cleanup_notes)
🤖 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/runner/transport.rs` around lines 316 - 324, In the bounded cleanup path
in drain_reader_until_cleanup_deadline usage, the current ordering drains stdout
before stderr even though only stderr is returned, which can waste the remaining
deadline if stdout stays open. Update the cleanup sequence in the transport
runner so stderr is drained first and its output/notes are preserved, then drain
stdout as best-effort discarded cleanup, keeping the existing use of
drain_reader_until_cleanup_deadline and the final stderr.output return behavior
intact.

@bnomei
bnomei merged commit c94b338 into main Jun 28, 2026
3 of 6 checks passed
@bnomei
bnomei deleted the devana-fixes branch June 28, 2026 14:59
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