Skip to content

fix(desktop): fail closed when adopted host-service has no version#3679

Merged
saddlepaddle merged 4 commits into
mainfrom
fix/host-service-version-gate
Apr 23, 2026
Merged

fix(desktop): fail closed when adopted host-service has no version#3679
saddlepaddle merged 4 commits into
mainfrom
fix/host-service-version-gate

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Apr 23, 2026

Summary

  • Version gate only killed adopted hosts when fetchHostVersion returned a string below MIN_HOST_SERVICE_VERSION. If the host predated host.info entirely, the helper returned null and we silently kept the stale service, producing 404s on newer routes (project.findByPath, etc).
  • Flip the guard to fail-closed so any host that can't prove it meets the minimum is killed and its manifest removed; next spawn brings up a compatible service.

Test plan

  • Start app with a manifest pointing at a pre-host.info host-service binary → confirm it gets killed and respawned (log: Adopted service version unknown < 0.2.0, killing).
  • Start app with a matching host-service → confirm it adopts cleanly with no respawn.
  • Confirm project.findByPath works on a fresh launch.

Summary by cubic

Fail closed when adopting a host-service that can’t prove it meets the minimum version, preventing 404s on routes like project.findByPath.

  • Bug Fixes

    • Fail-closed adoption: if version is missing or below the minimum (semver check), kill the process and remove its manifest so the next spawn is compatible.
    • Logs now show “version unknown” when not reported.
  • Refactors

    • Use semver for version checks; malformed versions are treated as unsupported.
    • Inline the version check to simplify control flow and logging.

Written for commit 8ec6d07. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Improved host service version handling: reported versions are now validated against the supported range using semantic versioning. Adopted services with missing/unknown versions or versions outside the supported range are terminated and their manifests removed. Termination logs now include a clear reason (e.g., actual version mismatch or "unknown"). Valid services continue to be marked running and monitored for liveness.

The version gate only killed the host when fetchHostVersion returned a
string less than MIN_HOST_SERVICE_VERSION. If the host lacked the
host.info route entirely (older releases), the helper returned null and
we silently adopted it, producing 404s on project.findByPath and other
new routes.

Flip the guard to fail-closed: any host that can't prove it meets the
minimum version is killed and the manifest removed so the next spawn
brings up a compatible service.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

📝 Walkthrough

Walkthrough

Host-service adoption now validates reported versions with semver.satisfies(version, >=MIN_HOST_SERVICE_VERSION). Adopted processes with missing/unknown versions or versions that don't satisfy the constraint are terminated; termination logs include an explicit reason string derived from the reported version or "unknown".

Changes

Cohort / File(s) Summary
Host Service Adoption Logic
apps/desktop/src/main/lib/host-service-coordinator.ts
tryAdopt now uses isHostServiceVersionSupported(version) (semantic semver.satisfies) to decide adoption; if version is null/unknown or fails the semver constraint, the coordinator logs a reason (version ${version} < ${MIN_HOST_SERVICE_VERSION} or version unknown), SIGTERM the adopted process, and removes its manifest. If satisfied, marks instance running and starts liveness monitoring as before.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I sniff the versions in the night,
If unknown, I hop to set things right.
Semver checked with careful art,
A tidy log, then we depart.
Back to my burrow — carrot and heart. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately describes the main change: implementing fail-closed behavior when an adopted host-service lacks version information, which is the primary objective of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description includes a clear summary, test plan, type of change context, and additional auto-generated notes explaining the bug fix and refactoring approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/host-service-version-gate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 23, 2026

Greptile Summary

This PR fixes a fail-open vulnerability in the host-service adoption path: previously, when fetchHostVersion returned null (e.g. the running service predated the host.info route), the version guard version && version < MIN_HOST_SERVICE_VERSION evaluated to false and the stale service was silently kept — causing 404s on newer routes like project.findByPath. The fix flips the condition to !version || version < MIN_HOST_SERVICE_VERSION so any service that cannot prove it meets the minimum is killed and its manifest removed, triggering a fresh spawn of a compatible binary.

  • One-line change in tryAdopt in host-service-coordinator.ts — the guard logic is inverted and the log message improved with a ?? \"unknown\" fallback.
  • The removeManifest + SIGTERM path was already correct; only the condition that reaches it is changed.
  • No new dependencies, no interface changes.
  • The pre-existing lexicographic string comparison (version < MIN_HOST_SERVICE_VERSION) is not introduced by this PR and is only mildly risky at low version numbers (e.g. 0.10.x vs 0.2.x), but is out of scope here.

Confidence Score: 5/5

Safe to merge — a minimal, correct fail-closed guard fix with no interface or behavioral regressions for valid services.

The change is a single boolean condition flip that correctly addresses the described bug. The null/falsy case was the only missing branch; the existing kill/remove/respawn path was already sound. The only comment is a P2 log-message clarity suggestion that doesn't affect correctness.

No files require special attention.

Important Files Changed

Filename Overview
apps/desktop/src/main/lib/host-service-coordinator.ts Guard condition in tryAdopt flipped from fail-open (version && version < MIN) to fail-closed (`!version

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[tryAdopt called] --> B[readAndValidateManifest]
    B -->|null| C[return null]
    B -->|manifest| D[fetchHostVersion\nGET /trpc/host.info]
    D -->|network/HTTP error| E[version = null]
    D -->|pre-host.info binary\n404 / bad response| E
    D -->|valid response| F[version = string]

    E --> G{"!version ||\nversion < MIN_HOST_SERVICE_VERSION\n← CHANGED: was 'version &&'"}
    F --> G

    G -->|true – fail closed| H[SIGTERM pid\nremoveManifest\nreturn null]
    G -->|false – version OK| I[instances.set / adopt\nreturn Connection]

    style G fill:#f9c,stroke:#c66
    style H fill:#fcc,stroke:#c44
    style I fill:#cfc,stroke:#4c4
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/main/lib/host-service-coordinator.ts
Line: 298-301

Comment:
**Misleading log message for unknown version**

When `version` is `null`, the log line reads `Adopted service version unknown < 0.2.0, killing`, implying the version was compared and found to be too old. The actual reason is that the version couldn't be determined at all. Consider splitting the message to make root-cause clearer:

```suggestion
		if (!version || version < MIN_HOST_SERVICE_VERSION) {
			const reason = !version
				? `version unknown (host predates host.info)`
				: `version ${version} < ${MIN_HOST_SERVICE_VERSION}`;
			console.log(
				`[host-service:${organizationId}] Adopted service ${reason}, killing`,
			);
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(desktop): fail closed when adopted h..." | Re-trigger Greptile

Comment on lines +298 to +301
console.log(
`[host-service:${organizationId}] Adopted service version ${version} < ${MIN_HOST_SERVICE_VERSION}, killing`,
`[host-service:${organizationId}] Adopted service version ${
version ?? "unknown"
} < ${MIN_HOST_SERVICE_VERSION}, killing`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Misleading log message for unknown version

When version is null, the log line reads Adopted service version unknown < 0.2.0, killing, implying the version was compared and found to be too old. The actual reason is that the version couldn't be determined at all. Consider splitting the message to make root-cause clearer:

Suggested change
console.log(
`[host-service:${organizationId}] Adopted service version ${version} < ${MIN_HOST_SERVICE_VERSION}, killing`,
`[host-service:${organizationId}] Adopted service version ${
version ?? "unknown"
} < ${MIN_HOST_SERVICE_VERSION}, killing`,
if (!version || version < MIN_HOST_SERVICE_VERSION) {
const reason = !version
? `version unknown (host predates host.info)`
: `version ${version} < ${MIN_HOST_SERVICE_VERSION}`;
console.log(
`[host-service:${organizationId}] Adopted service ${reason}, killing`,
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/main/lib/host-service-coordinator.ts
Line: 298-301

Comment:
**Misleading log message for unknown version**

When `version` is `null`, the log line reads `Adopted service version unknown < 0.2.0, killing`, implying the version was compared and found to be too old. The actual reason is that the version couldn't be determined at all. Consider splitting the message to make root-cause clearer:

```suggestion
		if (!version || version < MIN_HOST_SERVICE_VERSION) {
			const reason = !version
				? `version unknown (host predates host.info)`
				: `version ${version} < ${MIN_HOST_SERVICE_VERSION}`;
			console.log(
				`[host-service:${organizationId}] Adopted service ${reason}, killing`,
			);
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/main/lib/host-service-coordinator.ts`:
- Around line 297-301: The current check in host-service-coordinator.ts uses
string comparison (version < MIN_HOST_SERVICE_VERSION) which breaks semantic
version ordering; update the logic in the block that references version and
MIN_HOST_SERVICE_VERSION to use a proper semver comparison (e.g., import and use
the semver.compare/gt/lt or semver.satisfies utilities) so that null/undefined
still triggers the fail-closed path but otherwise compare numerically (e.g.,
semver.lt(version, MIN_HOST_SERVICE_VERSION)) before logging and killing; ensure
you handle pre-release/build metadata correctly by using the semver library
rather than string comparisons.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7f4283a4-f8ad-4e64-8235-1219bcc33b01

📥 Commits

Reviewing files that changed from the base of the PR and between 4a1af2e and d1ba71d.

📒 Files selected for processing (1)
  • apps/desktop/src/main/lib/host-service-coordinator.ts

Comment thread apps/desktop/src/main/lib/host-service-coordinator.ts Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/main/lib/host-service-coordinator.ts">

<violation number="1" location="apps/desktop/src/main/lib/host-service-coordinator.ts:297">
P2: Use proper semver comparison instead of the `<` operator. String comparison is lexicographic, so `"0.10.0" < "0.2.0"` evaluates to `true` in JavaScript, meaning any host-service version with a double-digit component will be incorrectly killed. A simple numeric split-and-compare helper would fix this.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/desktop/src/main/lib/host-service-coordinator.ts Outdated
String comparison made "0.10.0" < "0.2.0" evaluate to true, which
would have killed any future double-digit minor version. Split the
version on dots and compare segments as integers.

Also split the adopt-killing log into two branches so the null case
reads "version unknown" instead of "version unknown < 0.2.0".
Replace the hand-rolled split-and-compare with `semver.satisfies`,
which already handles malformed input by returning false. The
`semver` package is already a dependency.
@saddlepaddle saddlepaddle merged commit b737cfa into main Apr 23, 2026
7 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

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