Skip to content

fix(runtime-host): preserve Windows startup diagnostics - #3238

Merged
M4n5ter merged 3 commits into
mainfrom
fix/windows-runtime-host-startup-diagnostics
Aug 19, 2026
Merged

fix(runtime-host): preserve Windows startup diagnostics#3238
M4n5ter merged 3 commits into
mainfrom
fix/windows-runtime-host-startup-diagnostics

Conversation

@M4n5ter

@M4n5ter M4n5ter commented Aug 19, 2026

Copy link
Copy Markdown
Member
English

Summary

Preserve actionable evidence when Windows Runtime Host startup fails:

  • carry Local IPC security failures across the detached Candidate boundary as a distinct, retryable diagnostic code;
  • retain bounded PowerShell stage, SID, exit, and exception evidence for direct Local IPC failures;
  • print a bounded Runtime Host root, registration, and ACL snapshot before installed CLI validation cleans up its isolated state.

Fixes #3237

Verification

  • Runtime Host typecheck and build
  • 23 focused startup, endpoint, and owned-Candidate tests
  • 46 Host-kernel tests
  • Biome, node --check, and git diff --check

The full Runtime Host suite reached 980/982; its two process-timing failures pass in isolation and are tracked in #3190 and #3239. The Windows workflows remain the authoritative platform validation for this draft.

AI use

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: OpenAI Codex assisted with implementation, tests, and validation. M4n5ter is the contributor of record.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No
中文

摘要

在 Windows Runtime Host 启动失败时保留可定位的证据:

  • 将 Local IPC 安全失败以独立且可重试的诊断码带过 detached Candidate 边界;
  • 为直接 Local IPC 失败保留有界的 PowerShell 阶段、SID、退出状态和异常证据;
  • 在已安装 CLI 验收清理隔离状态前,输出有界的 Runtime Host 根目录、注册和 ACL 快照。

修复 #3237

验证

  • Runtime Host typecheck 与 build
  • 23 项启动、endpoint 和 owned-Candidate 定向测试
  • 46 项 Host-kernel 测试
  • Biome、node --checkgit diff --check

Runtime Host 全量套件通过 980/982;两项进程时序失败均可在独立运行时通过,并已由 #3190#3239 跟踪。此 Draft PR 的 Windows workflow 是权威平台验证。

AI 使用

  • 没有生成式工具作出实质贡献
  • 生成式工具作出了实质贡献

工具与范围:OpenAI Codex 协助实现、测试和验证。M4n5ter 是记录在案的贡献者。

检查清单

  • 测试覆盖本次变更,并会在缺少变更时失败
  • lint、format、typecheck 和受影响测试在本地通过

本 PR 是否改变行为?

  • 是——已在摘要中说明

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c17f450f-ca9b-41ec-a572-6cb3e42fcc6d

📥 Commits

Reviewing files that changed from the base of the PR and between b03b85e and f5032f8.

📒 Files selected for processing (19)
  • package.json
  • packages/runtime-host/src/__tests__/candidate-cli.test.ts
  • packages/runtime-host/src/__tests__/candidate-startup-failure.test.ts
  • packages/runtime-host/src/__tests__/control-endpoint.test.ts
  • packages/runtime-host/src/__tests__/execution-candidate-main.test.ts
  • packages/runtime-host/src/__tests__/host-kernel.test.ts
  • packages/runtime-host/src/__tests__/startup-diagnostic.test.ts
  • packages/runtime-host/src/__tests__/startup-error.test.ts
  • packages/runtime-host/src/candidate-cli.ts
  • packages/runtime-host/src/candidate-startup-failure.ts
  • packages/runtime-host/src/client/connect-or-spawn.ts
  • packages/runtime-host/src/client/launcher.ts
  • packages/runtime-host/src/client/startup-error.ts
  • packages/runtime-host/src/control/endpoint.ts
  • packages/runtime-host/src/control/startup-diagnostic.ts
  • packages/runtime-host/src/execution-candidate-main.ts
  • scripts/release-cli-runtime-host-diagnostics.mjs
  • scripts/release-cli-runtime-host-diagnostics.test.mjs
  • scripts/smoke-release-cli-package.mjs

📝 Walkthrough

Problem solved

This PR preserves actionable Windows Runtime Host startup diagnostics instead of returning only INTERNAL_STARTUP_FAILURE.

It:

  • Carries Local IPC security failures across detached Candidate processes with local_ipc_security_failed and retryable exit code 77.
  • Records bounded PowerShell stage, SID, exit status, exception, stderr, and ACL evidence.
  • Correlates diagnostics with startup attempts and retains only the election-selected failure.
  • Collects storage, registration, control-path, root, and ACL evidence before isolated state is removed.
  • Redacts secrets and uses bounded, private, atomic diagnostic files.

Design and scope

The PR extends the existing Candidate startup-failure and Local IPC paths. It does not replace their classification or retry behavior.

It adds a parallel diagnostic evidence channel in the private control namespace. The channel supports cross-process persistence because the detached Candidate cannot return all evidence directly. Attempt IDs, root validation, election settlement, pruning, and conditional cleanup limit stale or incorrect records.

This is the smallest coherent solution shown by the diff. Cross-process persistence and attempt correlation are necessary to identify the selected Candidate failure. Bounded collection and read-only probes are necessary to preserve evidence without weakening the Local IPC trust boundary or mutating isolated state.

The new tests are required for serialization, election behavior, redaction, permissions, cleanup, ACL evidence, and release diagnostics. No clear deletion or simplification is identified without reducing regression coverage.

Complexity delta

  • Authorities: Adds startup-diagnostic files and read-only release diagnostics. Existing startup-failure classification remains authoritative.
  • States: Adds per-attempt, selected, settled, stale, and retired diagnostic states.
  • Branches: Adds handling for local_ipc_security_failed, diagnostic persistence, selection, cleanup, and bounded evidence collection.
  • Configuration: Adds no new configuration.
  • Public surface: Adds CandidateStartupFailureReport, CandidateStartupDiagnostic, diagnostic helpers, CandidateStartupDiagnosticError, and windowsPipeAclFailureDiagnostic. The candidate argument contract now requires startupAttemptId.
  • Test burden: Adds focused unit, integration, election, endpoint, startup-diagnostic, and release-diagnostic coverage.

The implementation increases local complexity, but the added states and APIs support required cross-process evidence and bounded cleanup. The total maintenance complexity remains justified by the diagnostic and retry requirements.

Validation

Validation included Runtime Host typecheck/build, focused startup and endpoint tests, Host-kernel tests, Biome, node --check, and git diff --check.

The Runtime Host suite passed 980 of 982 tests. Two process-timing failures remain tracked separately.

Review-relevant risks

  • The Candidate CLI contract changes because --startup-attempt-id is now required.
  • New exported runtime-host types, functions, and error behavior affect public contracts.
  • Windows startup failure handling now exposes additional diagnostic data and changes the handling of Local IPC security failures.
  • Diagnostic files and ACL probes affect security-sensitive filesystem behavior.
  • Release smoke validation now collects and renders Windows Runtime Host failure evidence.

Material changes to public contracts, user-visible behavior, security, or releases require independent human review under repository policy.

Required-check status is based on the stated validation summary and is not independently verified here. The person performing the merge must review the final diff, and a maintainer makes the final determination.

Walkthrough

Runtime Host startup failures now carry UUID-based attempt identities, persist bounded diagnostics, and participate in election-aware cleanup. Windows ACL failures expose structured evidence. Release smoke tooling collects and renders diagnostics before cleanup.

Changes

Runtime Host startup diagnostics

Layer / File(s) Summary
Candidate failure reporting
packages/runtime-host/src/candidate-cli.ts, packages/runtime-host/src/candidate-startup-failure.ts, packages/runtime-host/src/client/launcher.ts, packages/runtime-host/src/execution-candidate-main.ts, packages/runtime-host/src/__tests__/*candidate*
Candidates require validated startup-attempt IDs. Launchers propagate UUIDs, classify failures, capture logs, and persist failure reports.
Diagnostic persistence and retention
packages/runtime-host/src/control/startup-diagnostic.ts, packages/runtime-host/src/__tests__/startup-diagnostic.test.ts
Diagnostics use bounded schemas, redaction, atomic restrictive writes, validated reads, attempt selection, conditional clearing, and retention pruning.
Election diagnostic lifecycle
packages/runtime-host/src/client/connect-or-spawn.ts, packages/runtime-host/src/client/startup-error.ts, packages/runtime-host/src/__tests__/host-kernel.test.ts, packages/runtime-host/src/__tests__/startup-error.test.ts
Candidate election selects the final diagnostic, clears superseded reports, and retires diagnostics after successful connection. Local IPC security failures remain retryable.
Windows endpoint diagnostics
packages/runtime-host/src/control/endpoint.ts, packages/runtime-host/src/__tests__/control-endpoint.test.ts
Windows named-pipe ACL failures now include bounded structured metadata and truncated stderr without serializing the PowerShell command.
Release diagnostic collection and reporting
scripts/release-cli-runtime-host-diagnostics.mjs, scripts/release-cli-runtime-host-diagnostics.test.mjs, scripts/smoke-release-cli-package.mjs, package.json
Release tooling collects, renders, tests, and retires Runtime Host diagnostics during Windows smoke cleanup. The release check runs the new diagnostics test.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CandidateLauncher
  participant RuntimeHostCandidate
  participant ConnectOrSpawn
  participant DiagnosticCollector
  participant RuntimeHostSmoke
  CandidateLauncher->>RuntimeHostCandidate: launch with startupAttemptId
  RuntimeHostCandidate->>DiagnosticCollector: persist startup failure diagnostic
  RuntimeHostCandidate-->>ConnectOrSpawn: return failure report
  ConnectOrSpawn->>DiagnosticCollector: select or clear attempt diagnostic
  RuntimeHostSmoke->>DiagnosticCollector: collect and render failure evidence
Loading

Possibly related PRs

Suggested reviewers: astro-han, 1625567290

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/windows-runtime-host-startup-diagnostics

Comment @coderabbitai help to get the list of available commands.

Classify Local IPC security failures across the detached Candidate boundary without making them permanent. Preserve bounded PowerShell and installed-package ACL evidence so intermittent Windows startup failures remain actionable after cleanup.

Generated-by: Codex
Persist bounded, redacted Candidate startup evidence in the private control namespace and remove it after a successful election. Release validation now observes that authority and canonical registration state through independent read-only probes, preserving the failed filesystem state and explicit Windows owner/SDDL evidence.
@M4n5ter
M4n5ter force-pushed the fix/windows-runtime-host-startup-diagnostics branch from 383b40a to 16b1593 Compare August 19, 2026 08:50
Detached elections can observe several retryable Candidates, so a root-wide last-writer sidecar could disagree with the failure selected by the client. Bind each exit report to an attempt-scoped artifact, publish only the selected evidence, and retire or bound abandoned records after use.\n\nCollect Windows path security in one PowerShell process under the production 30-second budget so the failure reporter remains useful under the slow-start condition it is diagnosing.\n\nGenerated-by: Codex
@M4n5ter

M4n5ter commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Aug 19, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Late reports erase diagnostics 🐞 Bug ☼ Reliability ⭐ New
Description
When an election settles while a Candidate failure report is still pending, the late-report branch
deletes that attempt's diagnostic even though no Candidate failure was selected. A Candidate that
wrote actionable Local IPC evidence near the deadline can therefore be returned as a generic
startup_timeout or host_unresponsive with its evidence removed.
Code

packages/runtime-host/src/client/connect-or-spawn.ts[R277-281]

+                  if (electionSettled) {
+                    void clearCandidateStartupDiagnostic(
+                      capability.rootId,
+                      failure.startupAttemptId,
+                    ).catch(() => undefined);
Relevance

●●● Strong

Recent Runtime Host reviews accept fixes preventing asynchronous paths from masking real failure
outcomes.

PR-#2674
PR-#3079

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Candidate failure reports are attached asynchronously and counted as pending, but the election does
not wait for them before returning a generic deadline result. The finally block then marks the
election settled; when the report resolves, the cited branch unlinks its per-attempt file through
clearCandidateStartupDiagnostic, whose implementation performs the deletion.

packages/runtime-host/src/client/connect-or-spawn.ts[271-303]
packages/runtime-host/src/client/connect-or-spawn.ts[318-333]
packages/runtime-host/src/client/connect-or-spawn.ts[373-395]
packages/runtime-host/src/control/startup-diagnostic.ts[195-203]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A Candidate failure promise can resolve after the election has settled. The current late-report branch deletes that attempt's diagnostic even when the election selected no Candidate failure, losing the evidence this PR is intended to preserve.

## Issue Context
This is a fix-now issue and is distinct from stale shared-diagnostic cleanup: it affects a fresh per-attempt diagnostic whose report was pending at election settlement. Reuse the existing pending-report and diagnostic-selection seams rather than adding another diagnostic state or public surface; deletion or unconditional cleanup cannot satisfy the evidence-preservation invariant.

## Fix Focus Areas
- packages/runtime-host/src/client/connect-or-spawn.ts[271-303]
- packages/runtime-host/src/client/connect-or-spawn.ts[318-333]
- packages/runtime-host/src/__tests__/host-kernel.test.ts[210-257]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Early failures lose diagnostic 🐞 Bug ☼ Reliability ⭐ New
Description
execution-candidate-main ignores diagnostic-write failures, but writeCandidateStartupDiagnostic
assumes the per-root control directory already exists. A valid Candidate whose root validation fails
before ownership acquisition (for example root_identity_changed) therefore exits with the
classified code without persisting any diagnostic; the new regression test masks this by manually
creating that directory first.
Code

packages/runtime-host/src/execution-candidate-main.ts[R44-51]

+  if (rootId && startupAttemptId) {
+    await writeCandidateStartupDiagnostic({
+      rootId,
+      startupAttemptId,
+      failure,
+      error,
+      logs,
+    }).catch(() => undefined);
Relevance

●●● Strong

Concrete missing-directory precondition defeats the new diagnostic path; reliability fixes are
recently accepted.

PR-#2674
PR-#1755

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The candidate validates the root before invoking the owner-acquisition path. The storage authority
creates the per-root control directory only during that later owner path, whereas the diagnostic
writer opens a temporary file directly below that directory and never creates it. The newly added
subprocess test explicitly creates the directory, demonstrating the otherwise missing precondition.

packages/runtime-host/src/server/candidate.ts[18-28]
packages/storage/src/root-authority.ts[864-872]
packages/runtime-host/src/control/startup-diagnostic.ts[86-118]
packages/runtime-host/src/tests/execution-candidate-main.test.ts[47-52]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Candidate startup failures that occur before `tryAcquireStateRootOwner` cannot persist their startup diagnostic because the per-root control directory has not yet been created. The write error is intentionally swallowed, so installed-CLI diagnostics have no evidence for those failures.

## Issue Context
`startInteractiveRuntimeHostCandidate` validates the root before acquiring the owner, while owner acquisition is the existing path that creates the per-root control directory. The test currently creates that directory manually before launching the Candidate, which does not reflect this failure path.

## Fix Focus Areas
- packages/runtime-host/src/control/startup-diagnostic.ts[79-134]
- packages/runtime-host/src/execution-candidate-main.ts[41-53]
- packages/runtime-host/src/server/candidate.ts[18-38]
- packages/runtime-host/src/__tests__/execution-candidate-main.test.ts[43-76]

Reuse the storage authority's existing private-control-directory preparation seam (or expose a narrowly scoped equivalent) before writing the diagnostic, rather than adding an unchecked `mkdir` in the diagnostic writer: the directory must retain the storage authority's ownership/privacy validation. Extend the subprocess regression test so it does not pre-create the control directory and still observes the persisted diagnostic.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Diagnostic deletion races ✗ Dismissed 🐞 Bug ☼ Reliability
Description
Fix-now: clearSelectedCandidateStartupDiagnostic validates the selected file and then
unconditionally unlinks that same mutable pathname. A concurrent Candidate election can replace
startup-diagnostic.json after the read and before the unlink, so the release validator deletes the
newer failure evidence it did not collect.
Code

packages/runtime-host/src/control/startup-diagnostic.ts[R210-212]

+  const diagnostic = await readCandidateStartupDiagnostic(rootId);
+  if (!diagnostic || diagnostic.startupAttemptId !== expectedStartupAttemptId) return false;
+  await clearCandidateStartupDiagnostic(rootId);
Relevance

●●● Strong

Concrete shared-path TOCTOU can delete newer evidence; recent Runtime Host reviews favor lifecycle
and cleanup race hardening.

PR-#1934
PR-#3230

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The selected diagnostic has one shared filename. The clearer's identity check is separated from its
deletion, while selection independently replaces that filename, so an interleaving can delete a
different attempt: read attempt A; select attempt B; unlink selected pathname.

packages/runtime-host/src/control/startup-diagnostic.ts[176-193]
packages/runtime-host/src/control/startup-diagnostic.ts[205-213]
scripts/smoke-release-cli-package.mjs[809-815]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The release validation reporter reads a selected Candidate diagnostic and then clears it through a read-then-unlink operation. Another election can select a newer diagnostic between those operations, causing the reporter to delete evidence for that newer failure.

Delete the reporter's post-report retirement path instead of adding a cross-process synchronization mechanism: the smoke test's isolated root is subsequently cleaned up, so retaining the file until that cleanup preserves evidence and eliminates the race. Adding a lock would introduce new shared state and lock-recovery/test-maintenance burden solely to remove a file that is already in isolated disposable state.

## Issue Context
`selectCandidateStartupDiagnostic` publishes an attempt by renaming it to the common selected pathname. `clearSelectedCandidateStartupDiagnostic` first reads that pathname to compare its attempt ID, then later unlinks the pathname; neither operation is coordinated with selection.

## Fix Focus Areas
- scripts/smoke-release-cli-package.mjs[809-815]
- scripts/release-cli-runtime-host-diagnostics.mjs[156-167]
- packages/runtime-host/src/control/startup-diagnostic.ts[205-213]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View high (1)
4. Stale startup diagnostic reported ✗ Dismissed 🐞 Bug ≡ Correctness
Description
Fix-now: when an election ends as startup_timeout or host_unresponsive without a new Candidate
report, it returns without retiring the shared diagnostic selected by an earlier election. The
release collector then presents that stale file as the current startup failure and deletes it,
producing misleading Windows diagnostics.
Code

packages/runtime-host/src/client/connect-or-spawn.ts[R327-330]

+    return {
+      kind: 'failed',
+      reason: sawUnresponsiveEndpoint ? 'host_unresponsive' : 'startup_timeout',
+    };
Relevance

●●● Strong

Direct stale shared-diagnostic correctness gap; team recently accepts preserving primary Runtime
Host startup evidence.

PR-#2906
PR-#3230

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The ready path explicitly retires the shared selected diagnostic, while timeout/unresponsive and
other non-Candidate outcomes bypass that cleanup. The newly added release collector reads the shared
file without correlating it to the current election, so any retained file is emitted as current
evidence.

packages/runtime-host/src/client/connect-or-spawn.ts[227-242]
packages/runtime-host/src/client/connect-or-spawn.ts[318-345]
scripts/release-cli-runtime-host-diagnostics.mjs[132-145]
scripts/release-cli-runtime-host-diagnostics.mjs[156-167]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A non-Candidate election outcome can leave an earlier election's selected startup diagnostic at the shared path, so subsequent diagnostic collection attributes stale evidence to the current failure.

## Issue Context
Reuse the existing `retireCandidateStartupDiagnostic` seam rather than adding another diagnostic authority or identifier. Apply it to outcomes that supersede Candidate startup evidence, including timeout/unresponsive and the direct upgrade/blocking-incompatibility returns.

## Fix Focus Areas
- packages/runtime-host/src/client/connect-or-spawn.ts[236-242]
- packages/runtime-host/src/client/connect-or-spawn.ts[318-330]
- packages/runtime-host/src/client/connect-or-spawn.ts[336-345]
- packages/runtime-host/src/__tests__/host-kernel.test.ts[210-258]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: 🧠 Deep: Substantial new diagnostic, election, filesystem-concurrency, Windows security, and release-validation logic spans many independent paths, making redundant review materially valuable.

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit f5032f8 🧠 Deep

Results up to commit f5032f8 🧠 Deep


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Stale startup diagnostic reported ✗ Dismissed 🐞 Bug ≡ Correctness
Description
Fix-now: when an election ends as startup_timeout or host_unresponsive without a new Candidate
report, it returns without retiring the shared diagnostic selected by an earlier election. The
release collector then presents that stale file as the current startup failure and deletes it,
producing misleading Windows diagnostics.
Code

packages/runtime-host/src/client/connect-or-spawn.ts[R327-330]

+    return {
+      kind: 'failed',
+      reason: sawUnresponsiveEndpoint ? 'host_unresponsive' : 'startup_timeout',
+    };
Relevance

●●● Strong

Direct stale shared-diagnostic correctness gap; team recently accepts preserving primary Runtime
Host startup evidence.

PR-#2906
PR-#3230

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The ready path explicitly retires the shared selected diagnostic, while timeout/unresponsive and
other non-Candidate outcomes bypass that cleanup. The newly added release collector reads the shared
file without correlating it to the current election, so any retained file is emitted as current
evidence.

packages/runtime-host/src/client/connect-or-spawn.ts[227-242]
packages/runtime-host/src/client/connect-or-spawn.ts[318-345]
scripts/release-cli-runtime-host-diagnostics.mjs[132-145]
scripts/release-cli-runtime-host-diagnostics.mjs[156-167]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A non-Candidate election outcome can leave an earlier election's selected startup diagnostic at the shared path, so subsequent diagnostic collection attributes stale evidence to the current failure.

## Issue Context
Reuse the existing `retireCandidateStartupDiagnostic` seam rather than adding another diagnostic authority or identifier. Apply it to outcomes that supersede Candidate startup evidence, including timeout/unresponsive and the direct upgrade/blocking-incompatibility returns.

## Fix Focus Areas
- packages/runtime-host/src/client/connect-or-spawn.ts[236-242]
- packages/runtime-host/src/client/connect-or-spawn.ts[318-330]
- packages/runtime-host/src/client/connect-or-spawn.ts[336-345]
- packages/runtime-host/src/__tests__/host-kernel.test.ts[210-258]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Diagnostic deletion races ✗ Dismissed 🐞 Bug ☼ Reliability
Description
Fix-now: clearSelectedCandidateStartupDiagnostic validates the selected file and then
unconditionally unlinks that same mutable pathname. A concurrent Candidate election can replace
startup-diagnostic.json after the read and before the unlink, so the release validator deletes the
newer failure evidence it did not collect.
Code

packages/runtime-host/src/control/startup-diagnostic.ts[R210-212]

+  const diagnostic = await readCandidateStartupDiagnostic(rootId);
+  if (!diagnostic || diagnostic.startupAttemptId !== expectedStartupAttemptId) return false;
+  await clearCandidateStartupDiagnostic(rootId);
Relevance

●●● Strong

Concrete shared-path TOCTOU can delete newer evidence; recent Runtime Host reviews favor lifecycle
and cleanup race hardening.

PR-#1934
PR-#3230

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The selected diagnostic has one shared filename. The clearer's identity check is separated from its
deletion, while selection independently replaces that filename, so an interleaving can delete a
different attempt: read attempt A; select attempt B; unlink selected pathname.

packages/runtime-host/src/control/startup-diagnostic.ts[176-193]
packages/runtime-host/src/control/startup-diagnostic.ts[205-213]
scripts/smoke-release-cli-package.mjs[809-815]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The release validation reporter reads a selected Candidate diagnostic and then clears it through a read-then-unlink operation. Another election can select a newer diagnostic between those operations, causing the reporter to delete evidence for that newer failure.

Delete the reporter's post-report retirement path instead of adding a cross-process synchronization mechanism: the smoke test's isolated root is subsequently cleaned up, so retaining the file until that cleanup preserves evidence and eliminates the race. Adding a lock would introduce new shared state and lock-recovery/test-maintenance burden solely to remove a file that is already in isolated disposable state.

## Issue Context
`selectCandidateStartupDiagnostic` publishes an attempt by renaming it to the common selected pathname. `clearSelectedCandidateStartupDiagnostic` first reads that pathname to compare its attempt ID, then later unlinks the pathname; neither operation is coordinated with selection.

## Fix Focus Areas
- scripts/smoke-release-cli-package.mjs[809-815]
- scripts/release-cli-runtime-host-diagnostics.mjs[156-167]
- packages/runtime-host/src/control/startup-diagnostic.ts[205-213]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread packages/runtime-host/src/client/connect-or-spawn.ts
Comment thread packages/runtime-host/src/control/startup-diagnostic.ts
@M4n5ter
M4n5ter marked this pull request as ready for review August 19, 2026 10:15
@M4n5ter

M4n5ter commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

I have reviewed the changes in this PR, which mainly focus on the diagnosis of a CLI release with a limited impact scope, and I take responsibility for this result.

@M4n5ter
M4n5ter merged commit 4e2628b into main Aug 19, 2026
19 of 20 checks passed
@M4n5ter
M4n5ter deleted the fix/windows-runtime-host-startup-diagnostics branch August 19, 2026 10:16
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Preserve Windows Runtime Host startup diagnostics

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Preserve redacted, attempt-scoped evidence across detached Runtime Host Candidate failures.
• Classify Windows Local IPC security failures distinctly while keeping retries enabled.
• Capture bounded registration and ACL snapshots before release validation cleanup.
Diagram

graph TD
  L["Candidate Launcher"] --> C["Runtime Candidate"] --> P["ACL Helper"]
  P -->|error evidence| C
  C -->|write failure| A[("Attempt Artifacts")]
  C -->|exit report| E["Client Election"]
  E -->|select attempt| A
  A --> S[("Selected Diagnostic")] --> V["Release Validator"]
  E -->|successful cleanup| A
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Forward diagnostics through process streams
  • ➕ Avoids persistent filesystem artifacts
  • ➕ Keeps failure evidence directly attached to child-process events
  • ➖ Detached Candidates do not retain a reliable stderr consumer
  • ➖ Evidence can disappear after parent exit or validation cleanup
  • ➖ Concurrent attempts still require explicit correlation
2. Use one root-wide diagnostic file
  • ➕ Simpler storage and cleanup model
  • ➕ Requires fewer artifact-selection operations
  • ➖ Last-writer wins under concurrent Candidate retries
  • ➖ The file can disagree with the failure selected by the election
  • ➖ Cannot safely retire obsolete attempt evidence
3. Write to Windows Event Log
  • ➕ Provides durable, centralized operating-system diagnostics
  • ➕ Naturally survives process and workspace cleanup
  • ➖ Introduces Windows-only integration and permission requirements
  • ➖ Complicates cross-platform tests and installed-package inspection
  • ➖ Creates additional privacy and retention concerns

Recommendation: Keep the attempt-scoped private artifacts and explicit election selection. This design reliably correlates detached process exits with bounded evidence, avoids last-writer races, remains cross-platform, and limits retention through redaction, pruning, and success cleanup.

Files changed (19) +1450 / -126

Bug fix (8) +658 / -123
candidate-cli.tsRequire a Candidate startup attempt ID +8/-0

Require a Candidate startup attempt ID

• Accepts and validates a UUIDv4 startup-attempt flag so detached failures can be correlated with persisted evidence.

packages/runtime-host/src/candidate-cli.ts

candidate-startup-failure.tsClassify retryable Local IPC security failures +21/-2

Classify retryable Local IPC security failures

• Introduces attempt-correlated failure reports and a dedicated exit code for insecure endpoints. Restricts permanent failures to data incompatibility and migration blockers.

packages/runtime-host/src/candidate-startup-failure.ts

connect-or-spawn.tsSelect diagnostics from the elected failure +150/-98

Select diagnostics from the elected failure

• Tracks attempt IDs through concurrent Candidate reports, publishes evidence for the selected failure, and removes obsolete or successful-election artifacts. Local IPC failures remain eligible for retries until the election settles.

packages/runtime-host/src/client/connect-or-spawn.ts

launcher.tsCorrelate Candidate launches with UUIDs +22/-9

Correlate Candidate launches with UUIDs

• Generates a UUID for each Candidate process, passes it through the CLI, and attaches it to decoded startup failure reports.

packages/runtime-host/src/client/launcher.ts

startup-error.tsExpose Local IPC security diagnostic code +4/-0

Expose Local IPC security diagnostic code

• Adds an actionable, retryable client error message for failures securing the Runtime Host Local IPC endpoint.

packages/runtime-host/src/client/startup-error.ts

endpoint.tsPreserve bounded Windows ACL failure evidence +64/-11

Preserve bounded Windows ACL failure evidence

• Records the PowerShell stage, current SID, exception metadata, exit status, signal, and bounded stderr as an error cause. The diagnostic excludes the command and remains subject to the existing 30-second timeout.

packages/runtime-host/src/control/endpoint.ts

startup-diagnostic.tsPersist secure attempt-scoped startup diagnostics +366/-0

Persist secure attempt-scoped startup diagnostics

• Adds schema-validated, atomically written diagnostic artifacts containing bounded and redacted error chains and logs. Supports selecting, conditionally clearing, and pruning attempt records in the private control namespace.

packages/runtime-host/src/control/startup-diagnostic.ts

execution-candidate-main.tsWrite startup evidence before Candidate exit +23/-3

Write startup evidence before Candidate exit

• Captures recent Runtime Host logs and the classified error, then best-effort persists them under the Candidate's root and attempt IDs before returning its diagnostic exit code.

packages/runtime-host/src/execution-candidate-main.ts

Tests (8) +444 / -2
candidate-cli.test.tsCover Candidate startup attempt parsing +8/-0

Cover Candidate startup attempt parsing

• Updates Candidate CLI fixtures to pass a valid startup-attempt UUID and verifies it is returned by the parser.

packages/runtime-host/src/tests/candidate-cli.test.ts

candidate-startup-failure.test.tsVerify retryable Local IPC failure classification +20/-0

Verify retryable Local IPC failure classification

• Confirms insecure endpoint errors map to the distinct Local IPC security reason, survive exit-code conversion, remain retryable, and omit private details.

packages/runtime-host/src/tests/candidate-startup-failure.test.ts

control-endpoint.test.tsVerify bounded ACL helper diagnostics +32/-0

Verify bounded ACL helper diagnostics

• Tests structured PowerShell failure metadata, the stderr byte limit, and exclusion of the invoked command.

packages/runtime-host/src/tests/control-endpoint.test.ts

execution-candidate-main.test.tsVerify detached Candidate evidence persistence +48/-0

Verify detached Candidate evidence persistence

• Adds an integration test proving a valid Candidate invocation writes attempt-correlated startup evidence despite the detached stderr boundary.

packages/runtime-host/src/tests/execution-candidate-main.test.ts

host-kernel.test.tsVerify election-correlated diagnostic selection +65/-2

Verify election-correlated diagnostic selection

• Updates mocked failure reports with attempt IDs and verifies the election publishes its chosen Candidate's artifact while removing an unselected artifact.

packages/runtime-host/src/tests/host-kernel.test.ts

startup-diagnostic.test.tsTest startup diagnostic lifecycle and safeguards +79/-0

Test startup diagnostic lifecycle and safeguards

• Covers bounded redacted persistence, private permissions, attempt selection, conditional cleanup, and pruning of stale records.

packages/runtime-host/src/tests/startup-diagnostic.test.ts

startup-error.test.tsVerify actionable Local IPC startup error +6/-0

Verify actionable Local IPC startup error

• Ensures Local IPC security failures expose their diagnostic code without becoming permanent reconnect failures.

packages/runtime-host/src/tests/startup-error.test.ts

release-cli-runtime-host-diagnostics.test.mjsTest release diagnostic collection and retirement +186/-0

Test release diagnostic collection and retirement

• Verifies canonical installed-package evidence collection, batched Windows ACL probing, selected-artifact retirement, and non-mutating behavior when root validation fails.

scripts/release-cli-runtime-host-diagnostics.test.mjs

Other (3) +348 / -1
package.jsonInclude diagnostics tests in release checks +1/-1

Include diagnostics tests in release checks

• Adds the Runtime Host release-diagnostics test suite to the repository's release verification command.

package.json

release-cli-runtime-host-diagnostics.mjsCollect installed Runtime Host failure snapshots +331/-0

Collect installed Runtime Host failure snapshots

• Adds read-only collection of storage authority, registration, startup artifact, path state, and batched Windows owner/SDDL evidence. Output is bounded, and the observed selected startup artifact can be conditionally retired afterward.

scripts/release-cli-runtime-host-diagnostics.mjs

smoke-release-cli-package.mjsReport Windows evidence before smoke cleanup +16/-0

Report Windows evidence before smoke cleanup

• Prints the bounded Runtime Host diagnostic snapshot when Windows release validation fails, then retires the exact startup artifact that was collected.

scripts/smoke-release-cli-package.mjs

Comment on lines +277 to +281
if (electionSettled) {
void clearCandidateStartupDiagnostic(
capability.rootId,
failure.startupAttemptId,
).catch(() => undefined);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Late reports erase diagnostics 🐞 Bug ☼ Reliability

When an election settles while a Candidate failure report is still pending, the late-report branch
deletes that attempt's diagnostic even though no Candidate failure was selected. A Candidate that
wrote actionable Local IPC evidence near the deadline can therefore be returned as a generic
startup_timeout or host_unresponsive with its evidence removed.
Agent Prompt
## Issue description
A Candidate failure promise can resolve after the election has settled. The current late-report branch deletes that attempt's diagnostic even when the election selected no Candidate failure, losing the evidence this PR is intended to preserve.

## Issue Context
This is a fix-now issue and is distinct from stale shared-diagnostic cleanup: it affects a fresh per-attempt diagnostic whose report was pending at election settlement. Reuse the existing pending-report and diagnostic-selection seams rather than adding another diagnostic state or public surface; deletion or unconditional cleanup cannot satisfy the evidence-preservation invariant.

## Fix Focus Areas
- packages/runtime-host/src/client/connect-or-spawn.ts[271-303]
- packages/runtime-host/src/client/connect-or-spawn.ts[318-333]
- packages/runtime-host/src/__tests__/host-kernel.test.ts[210-257]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +44 to +51
if (rootId && startupAttemptId) {
await writeCandidateStartupDiagnostic({
rootId,
startupAttemptId,
failure,
error,
logs,
}).catch(() => undefined);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Early failures lose diagnostic 🐞 Bug ☼ Reliability

execution-candidate-main ignores diagnostic-write failures, but writeCandidateStartupDiagnostic
assumes the per-root control directory already exists. A valid Candidate whose root validation fails
before ownership acquisition (for example root_identity_changed) therefore exits with the
classified code without persisting any diagnostic; the new regression test masks this by manually
creating that directory first.
Agent Prompt
## Issue description
Candidate startup failures that occur before `tryAcquireStateRootOwner` cannot persist their startup diagnostic because the per-root control directory has not yet been created. The write error is intentionally swallowed, so installed-CLI diagnostics have no evidence for those failures.

## Issue Context
`startInteractiveRuntimeHostCandidate` validates the root before acquiring the owner, while owner acquisition is the existing path that creates the per-root control directory. The test currently creates that directory manually before launching the Candidate, which does not reflect this failure path.

## Fix Focus Areas
- packages/runtime-host/src/control/startup-diagnostic.ts[79-134]
- packages/runtime-host/src/execution-candidate-main.ts[41-53]
- packages/runtime-host/src/server/candidate.ts[18-38]
- packages/runtime-host/src/__tests__/execution-candidate-main.test.ts[43-76]

Reuse the storage authority's existing private-control-directory preparation seam (or expose a narrowly scoped equivalent) before writing the diagnostic, rather than adding an unchecked `mkdir` in the diagnostic writer: the directory must retain the storage authority's ownership/privacy validation. Extend the subprocess regression test so it does not pre-create the control directory and still observes the persisted diagnostic.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit f5032f8

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.

ci(windows): diagnose flaky Runtime Host Local IPC startup failures

1 participant