Skip to content

bug(desktop): explicit Quit Maka leaves the local Runtime Host holding the State Root writer #3703

Closed

Description

@M4n5ter
English

What happened

Explicitly quitting Maka Desktop can leave the Desktop-owned ephemeral Runtime Host alive when durable residency exists. The surviving Host continues to hold the sole State Root writer, so a later Desktop generation or compatibility epoch cannot start against that root. Waiting does not converge while recurring durable work keeps the old Host resident; the current workaround is to identify and terminate the process manually.

Expected behavior: a full Quit Maka action is maintenance authorization for the Desktop-owned local ephemeral Host. It should retire that exact Host and release the writer before Desktop completes a clean exit. Closing only one Surface must remain distinct and must not stop a shared or operator-owned Host.

This is the urgent local Desktop slice of #3231.

How to reproduce

  1. Start Desktop with its local ephemeral Runtime Host.
  2. Create durable residency, such as an active recurring Scheduled Task.
  3. Explicitly quit Desktop without manually terminating the Host.
  4. Confirm that the Host remains alive and still owns the State Root writer.
  5. Start a later Desktop generation or compatibility epoch against the same State Root.
  6. Observe that Runtime Host election / State Root writer acquisition cannot complete. If the conflict UI is reached, Wait cannot converge while recurring residency remains.
  7. Terminate the surviving Host manually and observe that startup completes.

Environment

Logs, screenshots, or additional context

Reproduction evidence

The macOS reproduction recorded:

  • surviving Host: PPID 1 while retaining the State Root writer;
  • stale Electron singleton owner PID gone while SingletonSocket remained;
  • runtime.sqlite / runtime.sqlite-wal still open;
  • the next Desktop launch blocked in Runtime Host election / writer acquisition;
  • terminating the surviving Host allowed startup to complete immediately.

#3231 contains a Windows reproduction where an active Scheduled Task kept the epoch 24 Host resident and the epoch 25 Desktop could offer only Wait or Cancel Startup.

Verified current implementation

  • before-quit runs closeRuntimeHostDesktop.
  • closeRuntimeHostDesktop calls runtimeHostManager.close().
  • RuntimeHostDesktopManager.close() closes reconnect lifecycles and Client resources, but does not request retirement of the owned local Host.
  • The only Desktop path that currently invokes authenticated host.upgrade.prepare and waits for the exact adopted Host PID to exit is prepareForUpdate.

Durable residency therefore turns explicit Desktop quit into Surface-only disconnection at the Runtime Host boundary.

Why #3348 did not fix this

#3348 explicitly left normal-quit Host survival untouched. Its proposed registration/PID residue sweep was removed after review because PID termination could not be bound atomically to the authenticated Host; the PR closed with no functional fix. This issue must not revive registration-only PID killing.

Required behavior

When Electron is performing an explicit full application quit:

  • treat the action as maintenance authorization for the Desktop-owned local ephemeral Host;
  • stop candidate launches and bind retirement to the exact adopted Host identity/epoch;
  • fence new admission, preserve durable intent, and settle or explicitly interrupt active execution under [Tracking] Runtime Host maintenance and single-writer retirement #3231;
  • wait until the Host releases Domain resources and the sole State Root writer before completing application exit;
  • leave local service-mode Hosts and managed remote Hosts running;
  • report a truthful, bounded failure instead of silently claiming clean quit if safe retirement cannot complete.

Window-only close and full Quit Maka must have explicit, tested product semantics on each platform.

Acceptance criteria

  • Native Quit Maka / Cmd+Q / an equivalent explicit full-quit path retires the exact Desktop-owned ephemeral Host even when a Scheduled Task, Goal, or Automation holds durable residency.
  • Desktop does not finish clean shutdown until writer release is observed, or reports an actionable retirement failure.
  • Starting Maka again against the same State Root does not require manual process termination.
  • Durable schedules and continuation intent recover when Maka starts again; no background Host work runs while Maka is fully stopped.
  • In-flight work is recovered from a supported durable checkpoint or recorded as explicitly interrupted; unresolved external effects remain result_unknown.
  • Managed remote and local service-mode Hosts are not retired by Desktop quit.
  • Tests cover idle and durable-resident local Hosts, active execution, retirement failure, and platform-specific window-close/full-quit mapping.

Architecture boundaries

Two Draft PRs cover complementary parts of one larger lifecycle problem:

#3254 is not limited to takeover of a “truly idle” incompatible Host: it records a real epoch-24 takeover while a scheduled-task residency existed. That capability is legacy-dependent because the current Host kernel's generation-takeover path requires #isTrueIdle(). The system therefore lacks a stable, forward-compatible management contract for every incompatible durable-resident Host.

Neither Draft PR defines the final lifecycle authority. The target architecture is one local Host reconciliation system with one lifecycle owner, one artifact identity, one epoch fence, and one transition state machine shared by Desktop, installed CLI, and TUI presentation adapters.

Non-goals

Part of #3231.

AI assistance disclosure

Codex assisted with investigation and drafting. The evidence covers the #3348 outcome, #3254 behavior, and the main quit/retirement paths.

简体中文

发生了什么

存在 durable residency 时,用户明确退出 Maka Desktop 后,Desktop 自己拥有的 ephemeral Runtime Host 可能仍然存活。该 Host 会继续持有唯一的 State Root writer,导致后续 Desktop generation 或 compatibility epoch 无法针对同一 State Root 启动。只要 recurring durable work 继续保持 Host 常驻,等待就不会自行收敛;当前 workaround 是由用户定位并手工终止该进程。

预期行为:完整的退出 Maka应当构成对 Desktop 所拥有本地 ephemeral Host 的维护授权。Desktop 应让该精确 Host 安全退场并释放 writer,然后才能完成 clean exit。只关闭一个 Surface 必须保持不同语义,不能停止共享或由 operator 管理的 Host。

这是 #3231 中紧急的本地 Desktop 切片。

如何复现

  1. 启动 Desktop 及其本地 ephemeral Runtime Host。
  2. 创建 durable residency,例如启用一个 recurring Scheduled Task。
  3. 明确退出 Desktop,但不要手工终止 Host。
  4. 确认 Host 仍然存活,并继续持有 State Root writer。
  5. 使用后续 Desktop generation 或 compatibility epoch 针对同一个 State Root 启动。
  6. 观察 Runtime Host election / State Root writer acquisition 无法完成。即使进入 conflict UI,只要 recurring residency 仍存在,等待也不会收敛。
  7. 手工终止残留 Host,观察 Desktop 随即完成启动。

环境

日志、截图或补充上下文

复现证据

macOS 复现记录包括:

  • 残留 Host 的 PPID 1,且仍持有 State Root writer;
  • Electron singleton owner PID 已消失,但 SingletonSocket 仍存在;
  • runtime.sqlite / runtime.sqlite-wal 仍保持打开;
  • 下一次 Desktop 启动阻塞在 Runtime Host election / writer acquisition;
  • 手工终止残留 Host 后,启动立即完成。

#3231 还包含 Windows 复现:active Scheduled Task 使 epoch 24 Host 保持常驻,epoch 25 Desktop 只能提供等待或取消启动。

已核查的当前实现

  • before-quit 调用 closeRuntimeHostDesktop。
  • closeRuntimeHostDesktop 调用 runtimeHostManager.close()。
  • RuntimeHostDesktopManager.close() 只关闭 reconnect lifecycle 和 Client resource,不会请求所拥有本地 Host 退场。
  • 当前 Desktop 中,只有 prepareForUpdate 会调用已认证的 host.upgrade.prepare,并等待精确的 adopted Host PID 退出。

因此,durable residency 会让明确的 Desktop 退出在 Runtime Host 边界上退化成只断开 Surface。

为什么 #3348 没有修复该问题

#3348 明确没有改变 normal-quit Host survival。评审发现其 registration/PID residue sweep 无法将 PID kill 与已认证 Host 原子绑定,因此删除了该方案;PR 关闭时没有留下功能修复。本 issue 不能重新引入只根据 registration/PID 杀进程的方案。

必需行为

Electron 执行明确的完整应用退出时:

  • 将该操作视为对 Desktop-owned local ephemeral Host 的维护授权;
  • 停止 candidate launch,并将 retirement 绑定到精确的 adopted Host identity/epoch;
  • 按 [Tracking] Runtime Host maintenance and single-writer retirement #3231 停止新 admission、保存 durable intent,并收敛或明确中断 active execution;
  • 等待 Host 释放 Domain resource 和唯一 State Root writer 后,再完成应用退出;
  • 不停止 local service-mode Host 和 managed remote Host;
  • 无法安全完成 retirement 时,报告真实且有界的失败,不能静默声称 clean quit。

每个平台都必须明确并测试“只关闭窗口”和完整退出 Maka的产品语义。

验收标准

  • 原生退出 Maka、Cmd+Q 或等价 full-quit 路径,即使存在 Scheduled Task、Goal 或 Automation durable residency,也会让精确的 Desktop-owned ephemeral Host 退场。
  • Desktop 只有观察到 writer release 后才能完成 clean shutdown;否则必须报告可操作的 retirement failure。
  • 再次针对同一 State Root 启动 Maka 时,不需要用户手工终止进程。
  • Durable schedule 与 continuation intent 在 Maka 再次启动后恢复;Maka 完全停止期间不运行后台 Host work。
  • In-flight work 从受支持的 durable checkpoint 恢复,或被明确记录为 interrupted;无法确认的 external effect 保持 result_unknown。
  • Desktop 退出不会让 managed remote 或 local service-mode Host 退场。
  • 测试覆盖 idle 与 durable-resident local Host、active execution、retirement failure,以及各平台 window-close/full-quit 映射。

架构边界

两个 Draft PR 分别覆盖同一个更大生命周期问题的互补部分:

#3254 并不限于接管“真正空闲”的不兼容 Host:它记录了 epoch 24 Host 仍有 scheduled-task residency 时的真实 takeover。该能力依赖 legacy Host 行为,因为当前 Host kernel 的 generation-takeover 路径要求 #isTrueIdle()。因此,系统仍缺少一套适用于所有未来不兼容且 durable-resident Host 的稳定管理契约。

两个 Draft PR 都没有定义最终 lifecycle authority。目标架构是一套本地 Host reconciliation 系统:由一个 lifecycle owner、一种 artifact identity、一道 epoch fence 和一个 transition state machine 统一支撑 Desktop、installed CLI 与 TUI presentation adapter。

非目标

属于 #3231。

AI 辅助声明

Codex 协助了调查与起草。证据覆盖 #3348 的最终结果、#3254 的行为,以及 main 的退出和 retirement 路径。

Activity

  1. self-assigned this
    on Aug 24, 2026
  2. Astro-Han commented on Aug 24, 2026

    @Astro-Han
    Contributor

    Proposal: make explicit Desktop Quit a first-class retirement intent alongside upgrade, reusing the same single drain authority.

    Current before-quit → closeRuntimeHostDesktop → manager.close() only closes lifecycles/clients and relies on idleGrace, while prepareForUpdate is the only Desktop path that fences retirement with host.upgrade.prepare(expectedHostEpoch) and waits for the adopted PID to exit. With durable residency (e.g. recurring Scheduled Task) this turns Quit into Surface-only disconnect and reproduces the orphan writer block seen in #3231 / this issue.

    Suggested minimal change: introduce RetirementIntent = 'disconnect' | 'quit' | 'upgrade' through host-kernel → desktop-manager, make Quit carry the observed expectedHostEpoch and block clean exit until writer lease release, keep Disconnect unchanged for shared/managed Hosts. This is complementary to #3254 which covers CLI/TUI generation negotiation.

  3. M4n5ter commented on Aug 24, 2026

    @M4n5ter
    MemberAuthor
    English

    Thanks — I agree with the diagnosis and with reusing one drain authority. Draft PR #3706 implements that direction, with one layering adjustment: it does not propagate RetirementIntent = disconnect | quit | upgrade into the Host kernel.

    disconnect is not a retirement operation, while quit and upgrade are Client-side reasons for requesting one. The stable Host boundary is instead:

    • retire the exact authenticated Host;
    • bind the request to the authenticated connection's hostEpoch;
    • explicitly choose whether active work may be interrupted.

    #3706 therefore adds a shared Client retirement adapter around the existing host.upgrade.prepare wire operation and keeps that historical wire name isolated there. The Desktop and managed-service owners map their product actions onto this contract. This avoids teaching the Host kernel an expanding list of Client/UI intents while still giving Quit and Update the same exact-Host drain mechanism.

    On the Desktop side, the manager retires only its owned local ephemeral Host. Local service-mode and remote Hosts return not_owned and remain running. Quit authorizes interruption, waits for the adopted PID to exit, and only then closes Client resources. Update keeps its existing two-step active-work confirmation and rollback behavior. Concurrent Update/Quit handoff is coalesced so the Host is not retired twice.

    If retirement cannot be confirmed, Quit is cancelled and Maka remains usable. The failure dialog reports the State Root, Host epoch, underlying cause, and the authenticated PID when available, with cross-platform manual-recovery wording; it never performs registration/PID-only termination.

    I also agree that this is complementary to #3254 rather than a replacement. #3706 covers retirement through a connected compatible owner; #3254 covers generation negotiation and exact-epoch takeover where the observed incompatible Host permits it. #3254 records a real epoch-24 takeover with a scheduled-task residency, while the current Host kernel's generation-takeover path requires #isTrueIdle(). The remaining gap is therefore a stable, forward-compatible management contract for future incompatible durable-resident Hosts, rather than an absolute absence of legacy takeover capability.

    One platform boundary is called out explicitly in the draft: closing the last window remains Surface-only on macOS. On Windows and Linux, the existing window-all-closed → app.quit() mapping treats last-window close as full application quit, so it follows the retirement path.

    简体中文

    感谢建议——我认同这里的根因判断,也认同复用同一个 drain authority。Draft PR #3706 已沿这个方向实现,但在分层上做了一处调整:没有把 RetirementIntent = disconnect | quit | upgrade 一路传入 Host kernel。

    disconnect 本身不是 retirement;quit 和 upgrade 则是 Client 请求 retirement 的产品侧原因。更稳定的 Host 边界是:

    • 让经认证的精确 Host 退场;
    • 将请求绑定到认证连接的 hostEpoch;
    • 显式选择是否允许中断 active work。

    因此,#3706 在现有 host.upgrade.prepare wire operation 外增加了共享 Client retirement adapter,并把这个历史 wire 名称隔离在适配器内部。Desktop owner 和 managed-service owner 再把各自的产品动作映射到该契约。这样既能让 Quit 与 Update 复用同一套精确 Host drain 机制,也避免 Host kernel 开始承载一个会不断扩张的 Client/UI 意图列表。

    在 Desktop 侧,manager 只会让自己拥有的本地 ephemeral Host 退场。local service-mode 和 remote Host 返回 not_owned,继续运行。Quit 会授权中断 active work,等待 adopted PID 退出,然后才关闭 Client 资源;Update 则保留已有的 active-work 二阶段确认与 rollback 行为。Update/Quit 并发交接会合并到同一个事务,避免对同一 Host 重复发起 retirement。

    如果无法确认安全退场,Quit 会被取消,Maka 保持可用。失败弹窗会显示 State Root、Host epoch、底层原因,以及可获得时经认证的 PID;手动恢复说明使用跨平台文案,并且不会执行只依据 registration/PID 的自动终止。

    我也认同本改动与 #3254 是互补关系,而不是替代关系。#3706 覆盖通过已连接兼容 owner 发起的 retirement;#3254 覆盖 generation negotiation,以及 observed incompatible Host 允许时绑定精确 epoch 的 takeover。#3254 记录了 epoch 24 Host 仍有 scheduled-task residency 时的真实 takeover,而当前 Host kernel 的 generation-takeover 路径又要求 #isTrueIdle()。因此真正缺少的是一套面向未来不兼容且 durable-resident Host 的稳定管理契约,而不是 legacy takeover 能力绝对不存在。

    Draft 中还明确列出了一项平台边界:macOS 上关闭最后一个窗口仍然只是关闭 Surface;Windows 和 Linux 沿用现有的 window-all-closed → app.quit() 映射,因此关闭最后一个窗口属于完整应用退出,会进入 retirement 路径。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions