Skip to content

Keep the meeting mic and system audio going through device switches, hiccups and failed recoveries - #1780

Merged
claude[bot] merged 24 commits into
mainfrom
claude/mic-device-change-recovery-lx5xm9
Sep 24, 2026
Merged

claude[bot] merged 24 commits into
mainfrom
claude/mic-device-change-recovery-lx5xm9

Conversation

@claude

@claude claude Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Justin · project thread

Before: switching speakers or headphones mid-meeting dropped about 5 seconds of your mic, and each mic restart on a surviving non-Bluetooth mic could flip AirPods into call mode. Pressing Stop during a mic restart threw away the last couple of seconds. If one mic recovery failed, the whole meeting ended. If the chosen mic couldn't start, the meeting failed to start even when the Mac's own mic was right there. On the system-audio side, a third of a second of the Mac being busy ended call audio for the rest of the meeting, a slow wake used up the one reconnect a later real stall needed, and every reconnect threw away its first buffer without padding for it.

After: the mic restarts about a quarter second after an output switch, reusing its engine when the pinned mic survives, so the AirPods aren't touched on that path. Stop mid-restart keeps that audio. A failed recovery keeps the meeting (and system audio) recording and retries, falling back to a built-in mic that can actually hear you (never a lid-closed MacBook mic, at start or later). A mic that won't start falls back the same way instead of failing the meeting. System audio rides out a busy moment by reconnecting and padding the whole hole, slow wakes get their own reconnect, and reconnect pads now line up with the audio clock.

Not part of 1.1.62. Up to date with main (after 1.1.62, #1785, #1772, #1797, #1799, #1790, #1802).

Why

Audio follow-ups from the 1.1.62 review: the ~5s mic gap per output switch, one failed recovery ending the meeting, no built-in fallback at start, clip-removal log noise, the fresh-engine AirPods flip found in the #1771 diagnosis, deep review M3, M4, M5, M6, M7, M8, M11 and M14, plus four independent reviews of this branch (mic side, system-audio side, a full-PR pass, and the next-release deep review in reviews/next-release/1780.md), a verification pass, and a closing review (reviews/next-release/1780-closing.md: ready once CI is green). Every should-fix item is in; the follow-ups are listed under Notes.

Product Impact

  • Affects: meetings
  • Lane: meeting reliability
  • Why this matters: people switch AirPods, speakers and displays mid-call all the time, and losing their own voice, the other side, or the whole meeting is the worst failure we have.

What changed

Mic

  • Route change → immediate mic restart. Audio observes AVAudioEngineConfigurationChange. After a 0.25s settle it reads isRecording on main, then checks off main whether the changed engine is the live meeting graph and whether it stopped; if so it runs recoverFromDeviceChange. Pure decision in MicEngineConfigurationChangePolicy: ignores dictation's engine and detached graphs, ignores sleep, waits if another recovery is running, leaves a running engine alone (only a running engine can be "still flowing"; a stopped one can still hand over a last tap block), leaves a route that flaps within 1s of the last recovery ending to the watchdog, and never goes past the watchdog's 5-attempt limit. Will-sleep now sets the sleep mark before releasing the call-audio tap, so no route-change recovery starts while the Mac goes to sleep.
  • In-place mic restart. A device-change recovery reuses the engine already bound to the pinned mic when that mic is still alive, is not Bluetooth and VP is off, instead of building a fresh AVAudioEngine whose input node briefly opens the macOS default input. Gate in MicInPlaceRestartPolicy. A failed in-place restart retries once with a fresh graph after 0.2s (not counted as another switch or attempt); after any failed attempt the next one builds fresh, and an in-place restart that comes back on a different input counts as failed. Fresh-graph paths (unplugged mic, retries, fallback, VP on, start) still touch the default input; that's Record the Mac mic directly so AirPods never flip into call mode #1784's job.
  • Failed recovery keeps recording (M3). Failure branches no longer call stop(); the watchdog retries and still gives up after 5 in a row. Fixed two latent bugs that made retries no-ops. Recovery bails before touching hardware when the recording no longer owns a mic file. A retry pads from the last frame the recording actually kept (MicRecoveryGapAnchorPolicy), so frames a failed attempt took and then deleted can't leave the mic seconds early.
  • Stop mid-recovery keeps the recovery audio (M4). The recovery segment is registered as soon as its writer is installed. A header-only segment (Stop before the first frame) merges as empty instead of "degraded", so raw mic WAVs aren't left on disk. Stop during the first-frame wait is logged as stale.
  • Built-in fallback. Start gets a third attempt on the best built-in mic; recovery tries it first after a failed attempt or when the mic never delivered a frame. Reason builtInFallbackAfterFailure.
  • Closed lid. With the lid closed (MacLidState, IOKit AppleClamshellState, unreadable = open) the MacBook's own mic (isLidMicrophone) is left out of every built-in pick: the start pick, the stabilization fallback and the post-failure fallback. The filter runs once, in bestBuiltInInput. The headphone-jack mic and display mics still count. MacLidState.swift, isLidMicrophone and the lidClosed: parameters are shared line for line with Record the Mac mic directly so AirPods never flip into call mode #1784, so the two PRs merge without a duplicate type (notes in project files release/merge-notes/1780-vs-1784.md).
  • Clip log noise. SpeakerClipExtractor.removeClipFile skips the warning when the file wasn't there.

System audio (CoreAudioSystemAudioCapture, CoreAudioTapBufferRing, Audio)

  • M5, overflow no longer ends system audio. Audio queued before the hole is delivered, the tap is rebuilt, and the pad covers the whole hole (the ring counts dropped frames, including when a format change cuts the overflow short); after 3 overflows it still ends cleanly, keeping the queued audio. An overflow found at Stop also keeps its queued audio now. An overflow while an earlier reconnect's pad is pending skips the held audio and keeps one pad. Ring holds 128 callbacks (~1.4s), prefaulted. Overflow reconnects report .fellBehind, which holds writes like a reconnect but doesn't count as a device switch or lower capture_quality. Recording takes a .latencyCritical activity (idle sleep allowed, ended on failure) so App Nap can't coalesce the 10ms drain timer into an overflow.
  • M6, slow wake keeps the stall budget. A wake/route/overflow reconnect that never delivers gets one noFirstBuffer reconnect of its own (warns like a stall, reports the same event kind as the reconnect it retries, own on-device count).
  • M7, one silent-tap rebuild per wake instead of three.
  • M8, reconnect pads line up. The ring records the HAL's input host time per callback; the pad is firstNewSample - lastKeptSampleEnd when both are stamped and plausible. The host now queues the pad and releases the write-hold on the thread that sends .gap, before the reconnect's first buffer arrives, so that buffer is no longer thrown away uncounted. The pad write is gated by the file queue's generation check, not a main-thread flag.
  • M11, faster rate-change detection. 50ms poll, plus an immediate check when the callback size changes.
  • M14, sleep release finishes before the sleep notice returns (waits up to 1s on the capture queue).
  • Telemetry (Telemetry for 1.1.62: call-audio failures and reconnects, permission prompt, warmup, crash builds #1781) seam: overflow and no-first-buffer reconnects get their own on-device counts; nothing new is forwarded off-device.

How I checked it

  • python3 scripts/dev/check-build-source-lists.py
  • Trial merges with Record the Mac mic directly so AirPods never flip into call mode #1784 (c906bd9) and Mic-only meetings don't start the call-audio tap #1787 (0c1fb4a): one MacLidState, conflicts only in keep-both hunks, resolutions written to project files release/merge-notes/.
  • CI (app-build, checks, spm-tests) on the current head: running now. There's no Swift toolchain in the authoring session, so CI is the first compile of the system-audio half and the new tests.
  • Tests: MicRecoveryFallbackTests (route-change decisions incl. stopped-engine tap block, attempt limit, running engine; fallback policy incl. lid closed at start and after failure, the jack mic and a localized MacBook name; in-place gate; gap anchor; writer handoff), AudioInitializationTests (segment register/unregister/Stop wins), MicRecordingFileMergerTests (header-only segment), CoreAudioSystemAudioCaptureTests (overflow keeps queued audio, pad covers dropped audio, long stall, overflow during a pending pad, repeated overflows, overflow at Stop keeps queued audio, only route reconnects report a device switch, host-clock pad, buffer-size format check, slow wake keeps the stall reconnect and warns, sleep release), SystemAudioRecoveryParityTests (.gap releases the hold before main runs; falling behind pads without a route change), CoreAudioTapBufferRingTests, ClipRemovalPolicyTests.
  • Manual check on hardware (plan below). No CI job exercises real CoreAudio.

Risk Review

  • Privacy / local-first behavior reviewed: no new off-device fields beyond the builtInFallbackAfterFailure category string for the existing selection_reason; new logs carry transport classes, counts and scratch file basenames only.
  • Storage path or migration impact reviewed: none.
  • Release/update impact reviewed: not in 1.1.62. Adds IOKit to the app, fast-test and package link lists.
  • Agent PRs stay draft until human review
  • No private transcripts, audio, tokens, personal paths, or customer data are included

Notes

  • Not done here, on purpose:
    • When the mic is gone for good (e.g. a Mac mini's only USB mic unplugged), the watchdog still stops the meeting after 5 tries. Keeping call audio going with a slow mic retry needs a visible "your mic stopped" cue first, so it's a separate change.
    • There's no on-screen cue yet when the meeting falls back to a built-in mic. It's logged only. The existing route warning says "Bluetooth mic is unstable", which is wrong for a USB mic, so a proper cue gets its own small UI PR.
    • If the lid-closed MacBook mic is itself the macOS input, automatic mode still keeps it (same as main today). Fixing that changes selection(), which Record the Mac mic directly so AirPods never flip into call mode #1784 also rewrites, so it's a follow-up.
  • .latencyCritical stays on for the recording: overflow from a coalesced drain timer is the failure M5 is about. Revisit if battery data says otherwise.
  • If Stop lands after a recovery's first frame but before its gap is finalized, the last segment keeps a pre-start gap estimate (tail up to ~2s early). Narrow window, last seconds only.
  • Ring memory is ~8 MB per system-audio tap (was ~2 MB); a HAL teardown failure leaks that much.
  • Merging with Mic-only meetings don't start the call-audio tap #1787: Audio.swift gap and sleep hunks plus one test rename git won't flag; see project files release/merge-notes/1787-vs-1780.md.

Hardware test (Justin's Mac; step-by-step copy in project files release/after-1.1.62-mic-and-call-audio-test.md):

  1. Meeting on Mac speakers, switch output to AirPods mid-sentence and back. No ~5s hole in your voice; log "Audio route change stopped the microphone; recovering now". (Also answers: does the config-change notice fire on an output-only switch?)
  2. AirPods as the macOS default input, meeting pinned to the MacBook mic: switch output. Expect "Restarting mic engine in place" and clean AirPods playback.
  3. Stop within ~2s of switching output. Last words in the transcript; no leftover _mic_recovery.wav.
  4. USB mic unplugged mid-meeting. Meeting keeps going with your voice (the new default mic, or "Falling back to the built-in microphone" if that fails).
  5. Two lid-closes with a video playing: both sides survive; no "could not reconnect".
  6. Lid closed on an external display, macOS input set to the USB or display mic: the meeting never records from the silent MacBook mic, at start or after a failed mic.
  7. New speaker clip: no "Failed to remove clip file" warning.

Agent handoff

COORD_DONE: BRIEF | this PR | mic + system audio recovery; M3-M8, M11, M14; all reviews addressed, closing review ready once CI green; follow-ups: mic-lost-for-good, fallback cue, lid-closed default input | none | build source lists; CI running | merge when CI green, then hardware test

🤖 Generated with Claude Code

https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b

- Watch AVAudioEngineConfigurationChange so switching the Mac's output or
  default device restarts the mic right away. Before, only the watchdog
  noticed, after its 3s stall check, so each switch lost up to ~5s of the
  user's voice.
- A failed mic recovery no longer stops the meeting. System audio keeps
  recording and the watchdog retries on its cooldown; it still gives up
  after 5 failed attempts in a row. A retry also works when the earlier
  attempt left no graph or no open mic segment, which used to make every
  later attempt return early and record silence forever.
- Fall back to the built-in mic when the chosen mic can't start (third
  graph attempt at meeting start and in recovery), when a previous
  recovery failed, or when the mic never delivered a frame.
- Don't warn "Failed to remove clip file" when there was no clip to remove.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
…s on main

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
A route change or stall now restarts the engine that is already bound to
the pinned mic when that mic is still there, instead of building a fresh
AVAudioEngine whose input node briefly opens the default input (AirPods
flip into call mode). A failed in-place restart immediately retries once
with a fresh graph. Bluetooth input, voice processing, processing
changes and moved formats keep the fresh-graph path.

The recovery segment is now listed with the recording as soon as its
writer is installed, so a Stop that lands during the restart merges it
instead of deleting the last seconds of mic audio (deep review M4).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
Keeps both recoverFromDeviceChange parameters (afterSystemWake from #1771,
freshGraphRequested from the in-place restart). The fresh-graph retry
carries afterSystemWake and is not counted as a second device switch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
- A ring overflow (the consumer fell behind for a few hundred ms) no
  longer ends system audio. The audio queued before the hole is kept,
  the tap is rebuilt and the hole is padded; after 3 overflows in one
  recording it still ends cleanly. The ring holds 128 callbacks (~1.4 s)
  instead of 32, and recording takes a latency-critical activity so App
  Nap can't stall the drain timer. (deep review M5)
- A wake or route reconnect that never delivers a buffer gets its own
  reconnect instead of spending the recording's one stall reconnect.
  (M6)
- The reconnect pad is measured from the HAL's host-time stamps of the
  last kept and first new sample, falling back to drain-tick times. (M8)
- The tap format is polled every 50 ms instead of 250 ms, and a change
  in buffer size confirms it right away. (M11)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
@claude claude Bot changed the title Keep the meeting mic going through output switches and failed recoveries Keep the meeting mic and system audio going through device switches, hiccups and failed recoveries Sep 23, 2026
Keeps the in-place mic restart ahead of #1767's settled-format rebuild
in device recovery, and gives the in-place graph the new
voiceProcessingEnabled field (always false there).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
… [skip ci]

- prepareForSystemSleep now waits (up to 1 s) for the release, so the
  tap is off the output before the will-sleep handler returns. (deep
  review M14)
- After a wake, a tap that stays silent while a call app plays is rebuilt
  once, not three times. A fresh tap that is still silent means the far
  end is quiet, and more rebuilds only cut real audio. (M7)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
The ring now counts the frames it drops after an overflow, and the
reconnect starts the interruption where that audio began. A long stall
used to get a pad of just the rebuild time, leaving call audio running
early against the mic.

An overflow while an earlier reconnect's pad is still pending skips the
queued audio (the host would drop it under the write-hold) and keeps the
first start so one pad covers everything. The last allowed overflow
keeps its queued audio before ending, the reconnect budget is only spent
once the queued audio is out, fail() ends the latency hint, and the ring
prefaults its storage so the IOProc takes no page faults.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
…ip ci]

A recovery segment is listed before its first frame now, so Stop can
close a header-only WAV. The merger counted it as skipped, reported
degraded fidelity and left the primary and every recovery segment on
disk. A segment with no payload lost nothing, so it no longer counts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
From the mic review of this branch:
- Recovery bails before touching hardware when the recording no longer
  owns a mic file, so a post-Stop recovery (and the unit tests with no
  engine) never builds a graph that opens the default input.
- The route-change observer now respects the 5-attempt limit, measures
  its flap guard from when the last recovery ended, and leaves a running
  engine alone, so it can't loop past the watchdog's give-up or swap a
  slow-starting mic for the built-in one.
- A fresh-graph retry after a failed in-place restart no longer spends a
  second attempt, and after any failed attempt the next one builds fresh.
- An in-place restart that comes back on a different input counts as a
  failed attempt.
- Stop during the first-frame wait is a stale recovery, not a failed one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
…ip ci]

The malformed-buffer overflow branches now count the buffer that trips
the latch, and a route change found after the ring latched overflow also
backs the interruption start over the dropped audio, so its pad covers
the whole hole instead of just the rebuild.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
Keeps this branch's overflow reconnect (its last-resort fail() now
carries #1781's buffer_overflow reason) and teaches #1781's reconnect
counter about the overflow and noFirstBuffer triggers: overflow gets
its own on-device count, a reconnect with no first buffer counts as a
stall reconnect.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
- Mic: a retry after a failed restart pads from the last frame the
  recording kept, not frames a failed attempt took and then deleted, so
  the mic can't end up seconds early against system audio.
- Mic: a tap block delivered after the route change stopped the engine
  no longer reads as a flowing mic; only a running engine can be flowing.
- Mic: recovery timestamps and the new gap anchor are lock-protected.
- System audio: an overflow reconnect reports .fellBehind, which holds
  writes like a reconnect but no longer counts as a device switch or
  lowers capture_quality. A reconnect with no first buffer reports the
  same kind as the reconnect it retries and gets its own on-device count.
- System audio: the dropped-audio pad fix-up runs inside the format
  reconnect, so an overflow cut short by a format change is padded too.
- TranscriptedCore CLAUDE.md describes the new recovery behavior.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
…[skip ci]

From the next-release deep review of #1780:
- S1 (M8 still open): the host released the system write-hold only on
  main after `.gap`, so the reconnect's first buffer (and any before
  main ran) was dropped and the new host-clock pad didn't cover it. The
  sending thread now queues the pad on the file queue and releases the
  hold before the capture hands over that buffer; main only records the
  gap metadata.
- S2: will-sleep marks the recording as sleeping before releasing the
  tap, which can now take up to a second, so a route-change mic recovery
  can't start in that window.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
… [skip ci]

From the next-release deep review of #1780:
- S3: with the lid closed the MacBook mic is cut off in hardware and
  records silence without ever failing, so the built-in fallback now
  skips the laptop's own mic while the lid is closed (read from IOKit's
  AppleClamshellState; any read failure counts as open) and takes a
  display or other built-in mic instead, or none. IOKit is linked
  explicitly in the app, fast-test and package builds.
- M4: an overflow found at Stop now keeps the audio queued before the
  hole (up to ~1.4 s with the bigger ring) instead of dropping the ring,
  unless an earlier reconnect's pad is still pending.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
…skip ci]

- The closed-lid check now covers the start pick and the stabilization
  fallback, not just the post-failure fallback, and spots the laptop mic
  by transport so localized names count.
- The call-audio gap pad no longer reads isRecording off main; the file
  queue's generation check already drops a stale pad.
- Removed the test-only recordSystemAudioGap, fixed its doc comment, and
  dropped the always-false anchor policy parameter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
…p ci]

- MacLidState moves to its own file, byte-identical to #1784's, so the
  merge sees one file instead of two definitions.
- The laptop-mic check is #1784's isLidMicrophone. The transport-based
  check dropped the headphone-jack mic, which still hears with the lid
  closed.
- The lid filter applies once, in bestBuiltInInput, through the same
  lidClosed parameters #1784 adds, for the start pick, the stabilization
  fallback and the post-failure fallback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
claude Bot pushed a commit that referenced this pull request Sep 24, 2026
A call app keeps its output running while nobody talks, so a tap hearing
zeros is often a quiet call (a lobby, a pause), not a lost one.

- Report after 60s instead of 30s.
- If the same tap then hears signal, it was a false alarm: the warning
  goes away and the meeting is not marked degraded. Only signal that
  needed a rebuild or a new output confirms the loss and keeps
  "Call audio is back" plus the degraded mark.
- One silent-tap rebuild per wake instead of three, matching #1780's M7.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgXMcLRGBtiSiybdgyD1VJ
The route-change check ran on a global queue and read the main-only
isRecording there. It now reads it on main after the settle delay and
hands it to the off-main check. Stop advances the generation, so a stop
in between still fails the sessionIsCurrent check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZkqPnF73GpVNv9N5LYX9b
…nge-recovery-lx5xm9

# Conflicts:
#	Sources/TranscriptedCore/Audio/Audio.swift
@claude
claude Bot marked this pull request as ready for review September 24, 2026 04:49
@claude
claude Bot merged commit 343e41a into main Sep 24, 2026
7 checks passed
@claude
claude Bot deleted the claude/mic-device-change-recovery-lx5xm9 branch September 24, 2026 04:49
claude Bot pushed a commit that referenced this pull request Sep 24, 2026
…c + call audio

Resolved per release/merge-notes/1796-vs-1780.md (both sides matched the
pre-resolved heads, so the saved resolved files were used as is): keep
#1796's SystemAudioSilenceWatch and drop #1780's WakeSilenceWatch; keep
all three reconnect counters; RecoveryTrigger gets both sides' cases, and
silentWhilePlaying maps to the systemWake event; overflow and
no-first-buffer rebuilds arm the rebuild watch like a stall does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgXMcLRGBtiSiybdgyD1VJ
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