Skip to content

feat(workspace): stop idle workspace runtimes after a set time - #1307

Merged
wesm merged 1 commit into
mainfrom
pr/idle-runtime-stop
Oct 12, 2026
Merged

wesm merged 1 commit into
mainfrom
pr/idle-runtime-stop

Conversation

@rodboev

@rodboev rodboev commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

With [workspaces] idle_runtime_stop_hours set, Forge stops a pty-owner workspace's agents, shell tabs and terminal after that many hours with no visible page view or typing, freeing the memory and hidden consoles they hold on Windows. Opening the page resumes each agent's conversation and starts fresh shells. The default, 0, turns it off.

With the other #1282 slices, resume covers Claude Code, Codex, OpenCode, Pi, Copilot, Cursor, Droid, Gemini, Hermes and Qwen Code; another agent needs only a thin adapter in kit. A question the agent was asking comes back as a normal message, so Claude Code's multiple-choice picker shows as plain text, as it does in herdr.

Light Dark
Parked agent pane parked-pane-light.png parked-pane-dark.png
Launcher session list parked-launcher-light.png parked-launcher-dark.png

Refs #1282, slice 2 (Stop idle runtimes on a timer)

@roborev-ci

roborev-ci Bot commented Oct 6, 2026

Copy link
Copy Markdown

roborev: Combined Review (56cf23e)

Verdict: Changes require fixes for 1 finding.

Medium

  • internal/workspace/localruntime/agent_resume.go:41: AgentResumable checks availability and resume arguments but ignores target.Kind. If a live terminal agent's target is hot-reloaded to protocol="acp", the idle pass can stop it even though Manager.launch rejects resuming it under an ACP target, leaving the runtime errored when the workspace is opened.

    Fix: Require target.Kind == LaunchTargetAgent before declaring a terminal agent resumable, and add coverage for a target protocol change while its original runtime remains live.


Reviewers: 2x codex, codex (security) | Synthesis: codex, 6s | Total: 6m24s

@roborev-ci

roborev-ci Bot commented Oct 6, 2026

Copy link
Copy Markdown

roborev: Combined Review (fccd83a)

Verdict: Changes require fixes for 1 finding.

Medium

  • internal/workspace/localruntime/agent_resume.go:41: If a running terminal agent's target is hot-reloaded to protocol="acp", AgentResumable can still return true. The idle pass then stops the agent, but Manager.launch rejects resuming it under an ACP target, leaving the runtime errored and its conversation unavailable until configuration is corrected.

    Fix: Require target.Kind == LaunchTargetAgent before declaring the agent resumable, and test a protocol change while the original runtime remains live.


Reviewers: 2x codex, codex (security) | Synthesis: codex, 7s | Total: 4m34s

@roborev-ci

roborev-ci Bot commented Oct 6, 2026

Copy link
Copy Markdown

roborev: Combined Review (27b6fa3)

Verdict: Changes require fixes for 1 finding.

Medium

  • internal/server/workspaceapi/idle_runtime.go:109: If configuration reload disables idle stopping between syncIdle() and the second configSnapshot(), stopIdle receives a zero timeout and stops every eligible shell, including recently viewed workspaces. The subsequent disable pass removes marks but leaves shells stopped and errored.

    Fix: Use one configuration snapshot for enabling idle stopping and selecting its timeout, reject nonpositive timeouts before a stop pass, and add a focused test for disabling between these operations.


Reviewers: 2x codex, codex (security) | Synthesis: codex, 18s | Total: 5m20s

@roborev-ci

roborev-ci Bot commented Oct 6, 2026

Copy link
Copy Markdown

roborev: Combined Review (a4ec715)

Verdict: No findings at or above medium severity.


Reviewers: 2x codex, codex (security) | Synthesis: codex, 5s | Total: 4m44s

@rodboev
rodboev marked this pull request as draft October 7, 2026 03:28
@rodboev rodboev self-assigned this Oct 7, 2026
@roborev-ci

roborev-ci Bot commented Oct 7, 2026

Copy link
Copy Markdown

roborev: Combined Review (70ea092)

Verdict: Changes require fixes for 2 findings.

Medium

  • internal/workspace/localruntime/agent_resume.go:52: AgentResumable accepts an existing absolute file without execute permission because resolveExecutable leaves absolute paths unchanged and the check only excludes directories. Idle stopping can then kill a live agent that cannot resume until its configuration or permissions are corrected.

    Fix: Validate executable usability with a platform-aware executable lookup, including absolute paths. Add coverage using an existing non-executable file.

  • internal/server/workspaceapi/idle_runtime.go:298: An in-progress idle pass continues using the old timeout if idle stopping is disabled or its timeout is increased during a blocked stop. Subsequent shell processes can be killed after stopping was disabled, and clearing their marks afterward leaves them errored rather than restoring them.

    Fix: Recheck the current timeout before each runtime and base-terminal stop. Abandon the pass when disabled and recalculate eligibility when the timeout changes. Add a focused Go test for a reload during a blocked stop.


Reviewers: 2x codex, codex (security) | Synthesis: codex, 8s | Total: 4m29s

@roborev-ci

roborev-ci Bot commented Oct 7, 2026

Copy link
Copy Markdown

roborev: Combined Review (0028701)

Verdict: No findings at or above medium severity.


Reviewers: 2x codex, codex (security) | Synthesis: codex | Total: 4m10s

@roborev-ci

roborev-ci Bot commented Oct 7, 2026

Copy link
Copy Markdown

roborev: Combined Review (4d83dca)

Verdict: Changes require fixes for 1 finding.

High

  • internal/agentactivity/store.go:131: After a Stop event records background tasks or scheduled wakeups, a subsequent idle_prompt notification without those arrays overwrites PendingWork with false. Once the idle timeout expires, stoppable permits shutdown despite outstanding work, terminating background tasks or preventing scheduled wakeups.

    Fix: Preserve pending-work state across notifications that do not provide an authoritative work inventory, and clear it only on an authoritative lifecycle update. Add a regression covering Stop with pending work followed by idle_prompt and an idle-stop pass.


Reviewers: 2x codex, codex (security) | Synthesis: codex, 6s | Total: 3m25s

@roborev-ci

roborev-ci Bot commented Oct 7, 2026

Copy link
Copy Markdown

roborev: Combined Review (d17d8b7)

Verdict: No findings at or above medium severity.


Reviewers: 2x codex, codex (security) | Synthesis: codex | Total: 4m24s

@roborev-ci

roborev-ci Bot commented Oct 7, 2026

Copy link
Copy Markdown

roborev: Combined Review (058199f)

Verdict: No findings at or above medium severity.


Reviewers: 2x codex, codex (security) | Synthesis: codex | Total: 5m19s

@mariusvniekerk

Copy link
Copy Markdown
Collaborator

Is this configurable? I very frequently run agents and their stopping condition is asking a question, right? Losing that question is extremely bad.

@mariusvniekerk

Copy link
Copy Markdown
Collaborator

I wonder if the better path is not just to push Windows users more towards the ACP ones, where I believe we have better resumption capabilities in any case. So, this feels like a feature that is likely just more harm than good.

@rodboev

rodboev commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

It doesn't actually lose the question. What the caveat refers to in this case is something like the AskUserQuestion tool which pops up the multiple-choice. And when you resume a session that stopped on that, it presents the question normally instead of the multiple choice. The functionality here mirrors herdr; the body is updated to reflect that.

It's also totally opt-in at the moment, and the default value of 0 means nothing closes automatically.

@rodboev
rodboev marked this pull request as ready for review October 7, 2026 13:33
@rodboev
rodboev requested review from mariusvniekerk and wesm October 7, 2026 13:33
@mariusvniekerk mariusvniekerk self-assigned this Oct 9, 2026
@roborev-ci

roborev-ci Bot commented Oct 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (94700c1)

Verdict: Changes require fixes for 2 findings.

Medium

  • internal/server/workspaceapi/idle_runtime.go:358: SessionStart followed by idle_prompt marks a never-prompted session StateDone, bypassing the StateIdle/Continued guard and allowing idle parking before a conversation exists. AgentResumable only checks command availability.

    Fix: Preserve independent evidence of a prompted or continued conversation across notifications and require it before parking. Test SessionStart followed by idle_prompt without a prompt.

  • internal/agentactivity/store.go:134: Overlapping hook requests can permanently lose PendingWork: a notification reads false, Stop publishes true for pending work, and the notification overwrites it with false. The unsynchronized read/merge/write sequence can allow idle parking to terminate pending work; later notifications preserve the corrupted value.

    Fix: Serialize each report's read/merge/write transaction and add a concurrency test proving that a non-Stop event cannot overwrite Stop's pending-work update.


Reviewers: 2x codex, codex (security) | Synthesis: codex, 6s | Total: 10m43s

@roborev-ci

roborev-ci Bot commented Oct 10, 2026

Copy link
Copy Markdown

roborev: Combined Review (2206b9b)

Verdict: Changes require fixes for 2 findings.

High

  • internal/workspace/localruntime/agent_resume_test.go:40: The ACP target in TestAgentResumableRequiresTerminalAgentTarget uses an unprovisioned "claude" executable. When Claude is absent from PATH, executable resolution makes the rejection assertion pass even if the target-kind guard is removed, masking the regression.

    Fix: Use os.Args[0] for the ACP target command, matching the working terminal target, so executable availability cannot mask a missing target-kind guard.

Medium

  • internal/workspace/localruntime/acp_owner.go:54: ACPReloadable checks only the saved LoadSession capability. If the saved executable is removed or loses execute permission while the owner remains running, idle cleanup still terminates it, and reopening fails when restoreACP launches the saved command.

    Fix: Validate that the executable in the saved ACP owner configuration can still run before parking. Keep the owner alive when validation fails and add a regression test.


Reviewers: 3x codex, codex (security) | Synthesis: codex, 7s | Total: 13m42s

@roborev-ci

roborev-ci Bot commented Oct 10, 2026

Copy link
Copy Markdown

roborev: Combined Review (db25dac)

Verdict: No findings at or above medium severity.


Reviewers: 3x codex, codex (security) | Synthesis: codex | Total: 12m13s

@wesm wesm self-assigned this Oct 11, 2026
@wesm

wesm commented Oct 11, 2026

Copy link
Copy Markdown
Member

reviewing

@roborev-ci

roborev-ci Bot commented Oct 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (7cbde5a)

Verdict: Changes require fixes for 1 finding.

High

  • internal/workspace/localruntime/agent_resume_test.go:40: TestAgentResumableRequiresTerminalAgentTarget uses "claude" for the ACP target without ensuring the executable exists. On machines without Claude installed, the assertion passes even if the target-kind guard is removed because executable resolution independently returns false, masking the protocol-change regression.

    Fix: Use os.Args[0] for the ACP target, matching the passing terminal-agent target, so only the target kind determines the result.


Reviewers: 3x codex, codex (security) | Synthesis: codex, 9s | Total: 12m0s

@roborev-ci

roborev-ci Bot commented Oct 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (e7a2b08)

Verdict: No findings at or above medium severity.


Reviewers: 3x codex, codex (security) | Synthesis: codex | Total: 10m19s

- fix(workspace): guard the stop-mark stat result for nilaway
- fix(workspace): only stop terminal agents whose target still resumes them
- fix(workspace): skip an idle pass whose timeout a reload cleared
- fix: stop idle workspace terminals until their next attach
- fix: keep idle terminal stops from blocking other workspaces
- fix: let idle ACP chats stop despite stale hook reports
- fix: preserve resumable conversations and clear deleted runtime state
- fix: keep compaction from marking an agent's conversation as resumable
- fix: drop the stop mark when a stopped runtime's tab is closed
- refactor: leave the base terminal backend check to the idle pass
- fix: keep idle stop tied to user input and resumable agents
- fix: keep idle stops aligned with live runtime settings
- fix: preserve pending agent work during idle cleanup
- fix: keep pending agent work alive across lifecycle reports
- fix: preserve pending work and parked session status
- fix: accept any background task shape from agent hooks
- fix: keep idle stop from parking agents Forge cannot resume
- fix: keep idle stop from parking unprompted agents or losing pending work
- test: keep idle stop tests passing on the current main
- fix: keep resuming idle-stopped runtimes from stalling other workspaces
- style: format the phone runtime discovery e2e test
- test: make the ACP resume check fail when the target-kind guard is gone

Generated with Claude Code (claude-opus-5-5)
Generated with Grok 4.7
Co-authored-by: Marius van Niekerk <marius.v.niekerk@gmail.com>
Co-authored-by: Wes McKinney <wesmckinn+git@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Grok <noreply@x.ai>
@wesm
wesm force-pushed the pr/idle-runtime-stop branch from e7a2b08 to bd861f7 Compare October 12, 2026 01:53
@roborev-ci

roborev-ci Bot commented Oct 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (bd861f7)

Verdict: No findings at or above medium severity.


Reviewers: 3x codex, codex (security) | Synthesis: codex | Total: 12m38s

@wesm
wesm merged commit 13fde2a into main Oct 12, 2026
18 checks passed
@wesm
wesm deleted the pr/idle-runtime-stop branch October 12, 2026 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants