Skip to content

🤖 feat: keep screen awake while agents are working - #4360

Merged
ThomasK33 merged 4 commits into
mainfrom
thomask33/keep-screen-awake
Sep 23, 2026
Merged

ThomasK33 merged 4 commits into
mainfrom
thomask33/keep-screen-awake

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds an opt-in Keep screen awake while agents are working setting (Settings → General → System, default off). While it is on and any local workspace is busy (streaming, a background bash monitor, or an active workflow run), the Electron main process holds a single powerSaveBlocker prevent-display-sleep blocker. It releases the blocker as soon as every workspace is idle, the setting is turned off, or the app quits.

Background

Long agent runs can let the display sleep or trigger the idle screen lock. This follows Codex Desktop's "prevent sleep while running" behaviour instead of an always-on toggle.

Implementation

  • src/desktop/keepAwake.ts: KeepAwakeController. Desktop startup does not wait for its initial activity seed. It is driven by events from workspace activity and config changes, with no timers. It holds exactly one blocker ID and asserts that isStarted is true after acquiring. It subscribes before seeding from getActivityList(), so live events received during seeding take precedence over the older snapshot. Electron main.ts owns it: startup failures are logged and never crash the app, and before-quit disposes it.
  • src/common/utils/workspaceActivity.ts: busy predicate (stream, bash monitor, or workflow run) used by the keep-awake controller. HeartbeatService is unchanged.
  • Config and oRPC: keepScreenAwake is written to disk only when true and removed when it is false. Adds config.updateKeepScreenAwake.
  • Settings copy promises only display and system sleep; the plan below predates that change. Electron does not guarantee suppression of the OS idle lock.
  • Settings UI: optimistic toggle with serialized saves. A failed save rolls back to the last confirmed value, and a stale in-flight save cannot override a newer choice. The switch follows config.onConfigChanged, so it stays in sync when the palette command runs while Settings is open.
  • Command palette: Toggle Keep Screen Awake.

Only local backend activity counts. Remote-connection windows and xum server mode persist the flag, but nothing reads it there.

Validation

  • Tests for the controller cover deduplication, toggling mid-stream, the seed race, and dispose. Also tested: router config round-trip, Settings hydration, persistence, and rollback, and the palette command.

  • make static-check passes on this head.

  • All 968 src test files were run once each, using the CI partition from pr.yml (15 isolated files and 953 in a shared process): 19,921 passed and 0 failed.

  • Known pre-existing failure: tests/ui/storybook/budget.test.ts (run only by local make test-unit, not CI) fails on this branch, on its base, and on current main. It counts 115 enabled story files and 605 estimated snapshots against hardcoded limits of 79 and 305. This PR adds no stories, so that failure is out of scope here.

  • Remote dogfood UAT on this exact SHA ran through Coder Agents, with real Electron on Xvfb and a loopback fixture model (no paid calls). These scenarios passed:

    • two busy workspaces sharing one blocker;
    • toggling off and on mid-stream, including 10 rapid toggles;
    • release on quit;
    • persistence across a restart;
    • the palette command;
    • a real background-bash monitor that keeps the blocker held.

    The Settings row renders correctly at desktop width and at 375px. A real xfce4-power-manager accepted org.freedesktop.PowerManagement.Inhibit and UnInhibit, and each call balanced 1:1 with the app log.

    Limits: the headless container cannot show that a physical display stays on or that DPMS or the idle lock is prevented. No owner for org.freedesktop.ScreenSaver or org.gnome.SessionManager could be obtained, so those routes were probed but not exercised. macOS and Windows were not tested.

Risks

Low. The feature is off by default. Watch for a leaked blocker, which would keep the display awake. The controller releases on every idle, toggle-off, and quit path, and the tests and UAT cover those paths.

Follow-ups (deferred from Codex review)

  • Pending ask_user_question: a stream paused on a question still counts as busy, per the accepted plan's definition of busy. Releasing while the agent waits for input needs a new backend activity signal.
  • Doc comment: the isWorkspaceActivityBusy comment wrongly says heartbeats share the predicate (they don't). Correct it in the next change touching this file.
  • XUM_ALLOW_MULTIPLE_INSTANCES: Config.onConfigChanged is process-local (pre-existing), so a toggle made in one instance reaches another only on that instance's next activity edge. This needs a cross-process config watcher.
  • Crash-orphaned workflow runs: these runs are still counted in activeWorkflowRunCount (pre-existing shared accounting, also used by the sidebar and heartbeats). The fix belongs in the workflow subsystem.

📋 Implementation Plan

Keep screen awake while agents are working

Result

Add an opt-in "Keep screen awake while agents are working" toggle (Settings → General). While it is on and any local workspace is busy, the Electron main process holds a powerSaveBlocker.start("prevent-display-sleep") blocker; it is released as soon as every workspace is idle. Semantics match Codex Desktop's Prevent sleep while running (chosen over Claude Desktop's always-on toggle). Default: off.

Net product code: ~200 LoC (+ ~150 LoC tests).

Behaviour contract

  1. busy(workspace) = activity.streaming || activeBashMonitorCount > 0 || activeWorkflowRunCount > 0 — the existing definition used by HeartbeatService (src/node/services/heartbeatService.ts onActivity). Sub-agent tasks are workspaces with their own streams, so they are covered automatically.
  2. Blocker is held iff config.keepScreenAwake === true && busySet.size > 0. Exactly one blocker id at a time; never leak or double-start.
  3. Reacts to: workspace activity events (start/stop/clear), config changes (toggle on mid-stream acquires immediately; toggle off releases immediately), app quit (release).
  4. prevent-display-sleep implies prevent-app-suspension (Electron docs), so system idle-sleep is blocked too. macOS → IOPM PreventUserIdleDisplaySleep; Windows → SetThreadExecutionState(ES_DISPLAY_REQUIRED|ES_SYSTEM_REQUIRED); Linux → D-Bus org.freedesktop.ScreenSaver.Inhibit / org.gnome.SessionManager.Inhibit.
  5. "Unlocked" = the idle-timeout screensaver/lock never triggers. It does not defeat a manual lock, lid-close sleep, or an already-locked screen (same as Codex/Claude).

Architecture

ConfigStore.onConfigChanged ──┐
                              ├──> KeepAwakeController.reconcile() ──> electron.powerSaveBlocker.start/stop
WorkspaceService "activity" ──┘        (src/desktop/keepAwake.ts, owned by src/desktop/main.ts)

Backend services already run in-process in Electron main (src/desktop/main.ts:753–770 loadServices()), so main can subscribe directly — no new IPC. The controller lives in src/desktop/ (Electron-only; server mode has no display to keep awake) and receives powerSaveBlocker by injection so it is unit-testable like desktopWindowManager.test.ts.

Implementation steps

1. Persist the setting (backend) → verify: router.test.ts round-trip passes

Mirror llmDebugLogs end-to-end:

File Change
src/common/config/schemas/appConfigOnDisk.ts (~L156) keepScreenAwake: z.boolean().optional() with a comment: holds a display-sleep blocker only while agents are working.
src/common/types/project.ts (~L95) keepScreenAwake?: boolean
src/node/config/index.ts parse (~L2045 parseOptionalBoolean), serialize (~L2150, write only when true, delete when false like chatTranscriptFullWidth L2597–2600), getConfig view (~L2572 keepScreenAwake: config.keepScreenAwake === true), updateKeepScreenAwake(enabled) via editConfig (~L2606), getter getKeepScreenAwakeEnabled(): boolean (~L3012).
src/common/orpc/schemas/api.ts keepScreenAwake: z.boolean() in getConfig output (~L2641); updateKeepScreenAwake: booleanToggleRoute (~L2776).
src/node/orpc/router.ts (~L467) updateKeepScreenAwake handler → context.config.updateKeepScreenAwake(input.enabled).
src/node/orpc/router.test.ts (~L336) "persists the keep-screen-awake config flag" mirroring the full-width test (true → persisted; false → key removed).

2. Shared busy predicate → verify: heartbeatService tests still green

  • New src/common/utils/workspaceActivity.ts: isWorkspaceActivityBusy(activity: WorkspaceActivitySnapshot | null | undefined): boolean (type from src/common/types/workspace.ts:52).
  • Replace the identical inline predicate in src/node/services/heartbeatService.ts onActivity with the helper (DRY; 3-line swap, no behaviour change).

3. KeepAwakeController (src/desktop/keepAwake.ts, new) → verify: keepAwake.test.ts green

export interface PowerSaveBlockerLike {          // structural subset of electron.powerSaveBlocker
  start(type: "prevent-display-sleep"): number;
  stop(id: number): boolean;
  isStarted(id: number): boolean;
}
export class KeepAwakeController {
  constructor(deps: {
    blocker: PowerSaveBlockerLike;
    isEnabled: () => boolean;                       // config.getKeepScreenAwakeEnabled()
    onEnabledChanged: (cb: () => void) => () => void; // config.onConfigChanged
    activity: Pick<WorkspaceService, "on" | "off" | "getActivityList">;
  });
  start(): Promise<void>;   // subscribe → seed from getActivityList() → reconcile
  dispose(): void;          // unsubscribe + release
  get isHoldingBlocker(): boolean;  // test/debug visibility
}
  • onActivity({ workspaceId, activity }): isWorkspaceActivityBusy(activity) ? busy.add(id) : busy.delete(id), then reconcile(). activity === null (workspace removed) clears the id.
  • Seeding: subscribe before awaiting getActivityList(); ids already touched by a live event during the await win over the (older) snapshot, so a stream that ended mid-seed cannot pin the blocker.
  • reconcile(): want = isEnabled() && busy.size > 0. Acquire: assert(blockerId === null), id = blocker.start("prevent-display-sleep"), assert(blocker.isStarted(id)), log.debug("keep-awake: acquired display-sleep blocker"). Release: blocker.stop(id), blockerId = null, log.debug("keep-awake: released ..."). Idempotent when state already matches.
  • No timers, no grace period (deterministic signals exist; AGENTS.md "avoid timing-based coordination").

4. Wire into Electron main (src/desktop/main.ts) → verify: typecheck + manual dogfood log lines

  • Import powerSaveBlocker from "electron" (existing import block ~L67–80); module-level let keepAwake: KeepAwakeController | null = null.
  • In loadServices() right after services.updateService.onStatus(...) (~L771): construct with stores.config.getKeepScreenAwakeEnabled, stores.config.onConfigChanged, services.workspaceService, powerSaveBlocker; await keepAwake.start() inside try/catch + log.error (startup must never crash).
  • In the first app.on("before-quit") handler (~L1402), after remoteConnectionManager?.dispose(): keepAwake?.dispose(); keepAwake = null;.

5. Settings UI (src/browser/features/Settings/Sections/GeneralSection.tsx) → verify: GeneralSection.test.tsx green, Storybook renders

  • New <h3> block "System" between "Terminal" (~L843) and "Archiving" (~L991), same markup as the "API Debug Logs" row (L1097–1109):
    • Title: Keep screen awake while agents are working
    • Description: Prevents display sleep and the idle screen lock while any chat is streaming or waiting on background bash or workflow activity. Released as soon as all agents are idle. Desktop app only.
    • <Switch aria-label="Toggle keep screen awake while agents are working" />
  • State: useState(false); hydrate from api.config.getConfig().keepScreenAwake in the existing config-load effect (~L335–368); handler mirrors handleLlmDebugLogsChange (~L475–502): optimistic set, api.config.updateKeepScreenAwake({ enabled }), revert on failure.
  • Always rendered (no isDesktopMode() gate) so tests/stories need no window.api stub; the description states the desktop scope.
  • Storybook mock src/browser/stories/mocks/orpc.ts: add keepScreenAwake option/state (~L172, 426, 578) to the getConfig payload (~L824) and an updateKeepScreenAwake handler (~L925). Existing GeneralSection/SettingsPage stories pick it up; expect a Pixel visual diff on the General section only.
  • GeneralSection.test.tsx: extend MockConfig/MockAPIClient (L27–48) and renderGeneralSection options; add "loads and persists the keep screen awake toggle" mirroring L609–625.

6. Command palette entry → verify: palette shows "Toggle Keep Screen Awake"

  • src/browser/utils/commandIds.ts: settingsToggleKeepScreenAwake: () => "settings:toggle-keep-screen-awake" as const.
  • src/browser/utils/commands/sources.ts (section.settings, pattern of the async updateChannel action L1566–1575): run: async () => { if (!p.api) return; const cfg = await p.api.config.getConfig(); await p.api.config.updateKeepScreenAwake({ enabled: !cfg.keepScreenAwake }); }, keywords ["awake", "sleep", "screen", "display", "lock", "power", "caffeinate"]. Satisfies the "every operation has a keyboard route" rule beyond the focusable Switch.

Tests

  • src/desktop/keepAwake.test.ts (new, bun; fake blocker records start/stop, fake EventEmitter activity source, mutable enabled flag + manual config-changed emitter):
    1. disabled + busy → never starts.
    2. enabled; ws A streaming → one start; ws B bash monitor → still one; A idle → held; B idle → one stop.
    3. workflow-run activity counts as busy; activity: null clears.
    4. toggle on while busy → acquires; toggle off while busy → releases; toggle on again → re-acquires with a fresh id.
    5. start() seeds from getActivityList() containing a streaming workspace → acquires; event during seed overrides snapshot.
    6. dispose() releases and later activity events do not start a blocker.
    7. start() asserts on a blocker whose isStarted is false (defensive path).
  • router.test.ts: round-trip (step 1).
  • GeneralSection.test.tsx: hydrate + persist (step 5).
  • No tautological tests (no assertions on label/description copy).

Acceptance criteria

  1. make static-check and make test-unit pass; new tests above pass.
  2. Fresh config → toggle off → starting a stream never calls powerSaveBlocker.start (log shows no keep-awake: lines).
  3. Toggle on → start a held stream → keep-awake: acquired logged once even with two concurrent busy workspaces → all idle → keep-awake: released logged once.
  4. Toggle off during a stream → immediate released; toggle back on → immediate acquired.
  5. OS-level assertion visible while held (macOS pmset -g assertions lists PreventUserIdleDisplaySleep for xum; Windows powercfg /requests DISPLAY; Linux dbus-monitor shows Inhibit/UnInhibit), and gone after release/quit.
  6. Setting survives restart (~/.xum/config.json has "keepScreenAwake": true; off state removes the key).
  7. Settings General section renders correctly at desktop and ~375px widths (toggle row does not overflow).

Dogfooding

  1. Unit gates: bun test src/desktop/keepAwake.test.ts src/node/orpc/router.test.ts then bun test ./src/browser/features/Settings/Sections/GeneralSection.test.tsx; finally make static-check.
  2. Desktop sandbox: XUM_E2E=1 make dev-desktop-sandbox DEV_DESKTOP_SANDBOX_ARGS="--clean-providers --clean-projects" as a monitored background bash (filter Vite|ready|Electron|ERROR|failed). Point the sandbox at the loopback OpenAI-compatible SSE fixture with a held first token (see project memory xum-local-uat-fixtures) so a stream stays busy deterministically without paid calls.
  3. UI check (agent-browser via the sandbox's Electron debug port): open Settings → General, snapshot -i, toggle the switch, screenshot desktop + 375px widths; attach_file both. Verify config.json in the sandbox XUM_ROOT gains keepScreenAwake: true.
  4. Behaviour check: start agent-browser record start; with toggle on, send a prompt to the fixture workspace → confirm keep-awake: acquired in the Electron log (XUM_LOG_LEVEL=debug); on Linux run dbus-monitor --session "interface=org.freedesktop.ScreenSaver" (or org.gnome.SessionManager) in parallel and capture the Inhibit call; release the held stream (or interrupt) → released + UnInhibit. Repeat with toggle off mid-stream. record stop; attach the video.
  5. Palette check: Ctrl+Shift+P → "Toggle Keep Screen Awake" flips the switch state in Settings (screenshot).
  6. Terminate the sandbox and monitors before finishing.

Accepted trade-offs / limitations

  • Only local backend activity counts. Windows opened through RemoteConnectionManager run agents on the remote host and are not tracked; toggling the setting inside such a window edits the remote's config.
  • Browser/server mode (xum server) persists the flag but nothing consumes it.
  • No always-on mode, grace period, or system-only (display-may-sleep) variant; can be layered later by widening keepScreenAwake to an enum without a migration (absent key = off).
  • Cannot prevent lid-close sleep on macOS or a user-initiated lock; matches Codex/Claude behaviour.

Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $85.22

Add an opt-in "Keep screen awake while agents are working" setting
(Settings → General → System, plus a "Toggle Keep Screen Awake" palette
command). While enabled and any local workspace is busy — streaming, or
waiting on an armed background bash monitor or an unfinished workflow run —
the Electron main process holds exactly one
`powerSaveBlocker.start("prevent-display-sleep")` blocker and releases it as
soon as every workspace is idle, the setting is turned off, or the app quits.

- `keepScreenAwake` persisted in ~/.xum/config.json (absent = off; turning
  the toggle off removes the key), exposed via `config.getConfig` /
  `config.updateKeepScreenAwake`.
- `isWorkspaceActivityBusy` (src/common/utils/workspaceActivity.ts) is the
  shared busy predicate matching the sidebar's "working" notion.
- `KeepAwakeController` (src/desktop/keepAwake.ts) subscribes to
  WorkspaceService "activity" events and config changes, seeds from
  `getActivityList()` with live events winning over the older snapshot,
  ignores goal-only activity pushes, and isolates listener failures so they
  never propagate into the WorkspaceService emit path. Injected blocker keeps
  it unit-testable without Electron.
- Default off; server mode persists the flag but nothing consumes it, and
  remote-backend windows are not tracked.

---

_Generated with `xum` • Model: `coder:anthropic/claude-fable-5-1` • Thinking: `xhigh` • Cost: `$5.40`_

<!-- mux-attribution: model=coder:anthropic/claude-fable-5-1 thinking=xhigh costs=5.40 -->
Restore the last confirmed value when a keep-awake config write fails.
Keep rapid writes ordered and ignore stale failures so a previous request
cannot overwrite the user's latest selection.

Add regression coverage for both initial values and overlapping writes
with successful and failed predecessors.

---

_Generated with `xum` • Model: `coder:openai/gpt-6-astra` • Thinking: `high` • Cost: `$23.21`_

<!-- mux-attribution: model=coder:openai/gpt-6-astra thinking=high costs=23.21 -->
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-23T06:37:38.184312Z d2da901 Manual request
🔒 Security Review ✅ Completed 2026-09-23T06:40:39.622277Z d2da901 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 335ca5a361

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/desktop/main.ts Outdated
Comment thread src/common/utils/workspaceActivity.ts
Comment thread src/desktop/main.ts
Comment thread src/browser/utils/commands/sources.ts
Comment thread src/browser/features/Settings/Sections/GeneralSection.tsx Outdated
Comment thread src/common/utils/workspaceActivity.ts
- Do not block desktop window creation on the initial activity seed; live
  events already reconcile while it is in flight, and dispose is safe mid-seed.
- Settings switch follows keep-awake changes made outside the section (e.g. the
  palette command), without overriding an in-flight local save.
- Stop promising idle-lock suppression; Electron only blocks display/system sleep.
- Correct the busy-predicate doc: ask_user_question pauses still count as busy.

---

_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high` • Cost: `$87.98`_

<!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=high costs=87.98 -->
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: acdd745839

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/features/Settings/Sections/GeneralSection.tsx
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: acdd745839

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

A config change that arrives while a Settings-originated keep-awake save is in
flight is now deferred and replayed once the last save settles, so a later value
accepted by the backend (e.g. from the palette command) is not lost.

---

_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high` • Cost: `$87.98`_

<!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=high costs=87.98 -->
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: d2da901b98

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: d2da901b98

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33

Copy link
Copy Markdown
Member Author

Review record for d2da901b98f9

  • Codex: 3 review rounds. Round 1 (335ca5a36): 3 findings fixed, 3 deferred with reasons. Round 2 (acdd74583): 1 regression from the round-1 fix, now fixed. Round 3 (d2da901b9): "Didn't find any major issues". Security review is clean on this head, and all 7 threads are resolved.
  • CI: every required check passes on this head (Required is green). The optional Pixel visual review is still pending.
  • Local: make static-check passes. Targeted tests pass: keepAwake 11/0, GeneralSection 37/0 (three runs), sources + heartbeatService 131/0.
    • On 335ca5a36, all 968 src test files ran once using the CI partition: 19,921 passed, 0 failed.
    • The local-only Storybook budget test fails the same way on main, as disclosed in the description.
  • Remote UAT: ran on 335ca5a36 (before the review fixes) and passed with the limits listed in the description. Further remote runs are paused until a shared-deployment audit event is attributed; that event is unrelated to this code.
  • Independent readiness check: a fresh agent recommended ready with tracked follow-ups. It flagged that the description wrongly said HeartbeatService uses the new predicate. The description is now corrected, and the misleading code comment is tracked in Follow-ups.
  • Correction: my round-1 thread reply said the busy predicate matches HeartbeatService. It doesn't: heartbeats check only activity.streaming. The predicate follows the accepted plan's definition of busy.
  • Why I stopped: all readiness gates are met on this head. The deferred items are listed under Follow-ups in the description. Merging is left to a maintainer.

@ThomasK33
ThomasK33 added this pull request to the merge queue Sep 23, 2026
Merged via the queue into main with commit 46d75f1 Sep 23, 2026
19 of 20 checks passed
@ThomasK33
ThomasK33 deleted the thomask33/keep-screen-awake branch September 23, 2026 07:55
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