Skip to content

feat(desktop): keep the machine awake while local agents work - #5535

Draft
leo-mathurin wants to merge 2 commits into
pingdotgg:mainfrom
leo-mathurin:feat/desktop-caffeinate
Draft

feat(desktop): keep the machine awake while local agents work#5535
leo-mathurin wants to merge 2 commits into
pingdotgg:mainfrom
leo-mathurin:feat/desktop-caffeinate

Conversation

@leo-mathurin

@leo-mathurin leo-mathurin commented Aug 6, 2026

Copy link
Copy Markdown

Fixes #663

Problem. Long agent turns die when the machine goes to sleep. #663 asked for a sleep inhibitor held only while a thread is running, using prevent-app-suspension so the display can still turn off.

What this does. Adds an off-by-default desktop setting, "Caffeinate while agents are running" (Settings > General, hidden on hosted web). While it is on and at least one agent hosted by this desktop (primary local or WSL backend) has an active turn, the renderer asks the main process to hold a single idempotent prevent-app-suspension blocker over the typed IPC bridge. Remote, SSH, and relay agents do not count since local sleep does not interrupt them. The assertion releases below 10% battery while discharging and re-acquires when charging or recovered. Toggling off releases immediately, and a crashed or closed renderer cannot leak the assertion.

Verified.

  • Live on macOS: pmset -g assertions shows the Electron assertion appear seconds after a turn starts and disappear when the turn settles; toggling off mid-turn releases immediately; nothing leaks after quit.
  • 85 focused unit tests (contracts default and patch, the pure keep-awake predicate, blocker idempotency and stale-id recovery); targeted typecheck and lint clean.
  • Windows and Linux go through the same cross-platform Electron API; if the OS ignores the inhibitor the feature degrades to a silent no-op. I could only test end to end on macOS, so a confirmation from a Windows user (powercfg /requests) or a Linux user (systemd-inhibit --list) while an agent runs would be very welcome.

Screenshots.

Before (Settings > General on main):
before

After, toggle off (default):
after-off

After, toggle on:
after-on

Implemented by Claude (Fable 5) via Claude Code.

Add an off-by-default "Caffeinate while agents are running" setting. While
enabled and a desktop-hosted agent is busy, the renderer asks the Electron
main process to hold a prevent-app-suspension power-save blocker, so system
sleep cannot interrupt a running turn. The display can still turn off, and
the assertion releases below 10% battery while discharging.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 28abbe2e-2a90-4bd9-a880-5f0b379ed8f2

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

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.

@github-actions github-actions Bot added the size:L 100-499 changed lines (additions + deletions). label Aug 6, 2026
if (typeof setKeepAwake !== "function") {
return;
}
const send = (value: boolean) => {

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.

🟡 Medium state/desktopCaffeinate.ts:143

send sets lastSentRef.current before awaiting setKeepAwake(value) and then swallows rejections, so a failed IPC call records the value as delivered. A transient rejection of setKeepAwake(true) is never retried while the agent keeps running, and a rejected setKeepAwake(false) during unmount leaves the main-process keep-awake blocker held. Consider updating lastSentRef only after the promise resolves and retrying or reconciling on failure.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/state/desktopCaffeinate.ts around line 143:

`send` sets `lastSentRef.current` before awaiting `setKeepAwake(value)` and then swallows rejections, so a failed IPC call records the value as delivered. A transient rejection of `setKeepAwake(true)` is never retried while the agent keeps running, and a rejected `setKeepAwake(false)` during unmount leaves the main-process keep-awake blocker held. Consider updating `lastSentRef` only after the promise resolves and retrying or reconciling on failure.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Hardened in e37eb60: a rejected setKeepAwake no longer latches its value as delivered; the catch resets lastSentRef so the next state evaluation resends, and the main-process handler is idempotent so duplicate sends are harmless.

We kept the ref update before the await on purpose: moving it after resolution makes the dedup racy under rapid flips (two in-flight sends can interleave and latch a stale value). We also skipped a retry loop: the realistic rejection paths were checked (handlers are registered app-globally before any window loads, the payload is a plain boolean, and Electron's powerSaveBlocker never throws; on Linux without DBus the inhibit no-ops at the OS level, which a renderer retry could neither detect nor fix), and the main process force-releases on renderer death and window close as a backstop.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

@github-actions github-actions Bot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label Aug 6, 2026
A rejected setKeepAwake call no longer latches its value as delivered:
the failed send is forgotten so the next state evaluation resends. The
main-process handler is idempotent, so a duplicate send stays harmless.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prevent sleep while running for increased reliability

1 participant