Skip to content

fix: native host cleanup#204

Merged
MihalyToth20 merged 2 commits into
mainfrom
dev
May 25, 2026
Merged

fix: native host cleanup#204
MihalyToth20 merged 2 commits into
mainfrom
dev

Conversation

@MihalyToth20

@MihalyToth20 MihalyToth20 commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Bug Fixes
    • Fixed native host process cleanup to safely handle port cleanup and prevent unintended termination of running server processes
    • Improved session ownership tracking for more reliable process management

Review Change Stack

@changeset-bot

changeset-bot Bot commented May 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 68b26e9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@calycode/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Introduces owner-token-based session tracking for the OpenCode native messaging host with disk persistence, adds health probes to detect unowned OpenCode servers, and refactors cleanup flows to only terminate managed or clearly OpenCode-like processes.

Changes

Native host ownership tracking and safer process cleanup

Layer / File(s) Summary
Import randomUUID and extend ownerToken type contract
packages/cli/src/commands/opencode/implementation.ts
Adds randomUUID import from node:crypto and extends LaunchOpencodeServerOptions with optional ownerToken field to track process ownership throughout the spawning chain.
Native host persistence helpers and health detection
packages/cli/src/commands/opencode/implementation.ts
Implements storage and retrieval of long-lived owner tokens in state directory, per-session metadata persistence (port, pid, ownerToken, updatedAt), restoration of managed PIDs from disk, and /global/health probe to detect likely OpenCode servers and version mismatches.
Native host startup with owner token initialization
packages/cli/src/commands/opencode/implementation.ts
Updates startNativeHost to initialize and restore the persisted owner token, restore managed PIDs from disk, register/unregister managed sessions with metadata file writes, and guard cleanup with cleanupTriggered flag.
Spawn server with owner token propagation
packages/cli/src/commands/opencode/implementation.ts
Updates startServer path to invoke launchOpencodeServer with the native host ownerToken so spawned OpenCode processes can be identified and tracked as belonging to this session.
Restart and stop flows with ownership-aware cleanup and health checks
packages/cli/src/commands/opencode/implementation.ts
Refactors restartServer and stop message handlers to use owner-aware killProcessOnPort with allowUnmanagedOpencode and allowedOwnerToken parameters. Before refusing actions, both check port health via /global/health probe; server is only terminated if owned or process name matches OpenCode/calyd patterns, otherwise an explicit error is returned.
Async message handler, stdin parsing, and cleanup finalization
packages/cli/src/commands/opencode/implementation.ts
Updates message handler to be async and awaits startServer/restartServer completions. Stdin parser invokes the async handler without awaiting. Stdin close calls cleanup with explicit reason. Cleanup logic prevents double-execution via cleanupTriggered flag and deletes per-port session metadata files before exit.
Release notes
.changeset/few-results-kneel.md
Changeset declares patch version bump for @calycode/cli with note about adjusted native host process cleanup.

Sequence Diagram

sequenceDiagram
  participant NativeHost
  participant StateDir as State Directory
  participant SessionMeta as Session Metadata
  participant HealthProbe as /global/health
  
  NativeHost->>StateDir: getOrCreateNativeHostOwnerToken
  StateDir-->>NativeHost: ownerToken (long-lived)
  
  NativeHost->>StateDir: restorePersisstedManagedPids
  StateDir-->>NativeHost: managed PIDs for this token
  
  Note over NativeHost: On spawn
  NativeHost->>SessionMeta: persist {port, pid, ownerToken, updatedAt}
  
  Note over NativeHost: On cleanup check
  NativeHost->>HealthProbe: probe port for server
  HealthProbe-->>NativeHost: server detected or not
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • calycode/xano-tools#198: Both PRs modify native OpenCode server launch paths, adding/altering launcher spawn planning and startup/cleanup behavior in the same code region.
  • calycode/xano-tools#194: Both PRs modify the native-host/server startup refactor around launchOpencodeServer, with this PR extending it with owner-token tracking while the other PR introduces centralized server launching.
  • calycode/xano-tools#190: Both PRs modify OpenCode/native-host startup and process spawn behavior, with this PR adding owner-token-aware cleanup while the other PR scopes child processes to a dedicated working directory.

Poem

A rabbit hops through ownership's domain,
Tracking tokens, spawned with care,
Health probes whisper "who owns this flame?"
Before the cleanup hammer swings with fair precision,
Sessions persist, no orphans remain,
Safety guards each native turn. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main change: implementing safer native host process cleanup with owner token tracking.
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.

✏️ 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 dev

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/cli/src/commands/opencode/implementation.ts

Oops! Something went wrong! :(

ESLint: 9.39.4

TypeError: Key "languageOptions": Key "parser": Expected object with parse() or parseForESLint() method.
at new Config (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0/node_modules/eslint/lib/config/config.js:436:10)
at [finalizeConfig] (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0/node_modules/eslint/lib/config/flat-config-array.js:212:10)
at FlatConfigArray.getConfigWithStatus (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:1409:55)
at FlatConfigArray.getConfig (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:1427:15)
at /node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0/node_modules/eslint/lib/eslint/eslint.js:630:27
at Array.map ()
at lintFilesWithoutMultithreading (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0/node_modules/eslint/lib/eslint/eslint.js:628:13)
at ESLint.lintFiles (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0/node_modules/eslint/lib/eslint/eslint.js:1060:20)
at async Object.execute (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0/node_modules/eslint/lib/cli.js:428:14)
at async main (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0/node_modules/eslint/bin/eslint.js:175:19)


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/commands/opencode/implementation.ts (1)

1394-1423: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Serialize native-host commands instead of fire-and-forget async handlers.

handleMessage now awaits long-running startServer / restartServer, but the parser still dispatches each message with void handleMessage(msg). If Chrome sends start, restart, or stop back-to-back, those handlers can race on serverProc, managedSessions, and managedPids, leading to out-of-order status messages and killing the wrong process.

Suggested queuing approach
+   let messageQueue = Promise.resolve();
+
    process.stdin.on('data', (chunk) => {
      logger.log('Received data chunk', { length: chunk.length });
...
                try {
                   const msg = JSON.parse(messageData.toString());
-                  void handleMessage(msg);
+                  messageQueue = messageQueue
+                     .then(() => handleMessage(msg))
+                     .catch((err) => {
+                        logger.error('Queued message handling failed', err);
+                        sendMessage({ status: 'error', message: 'Internal error processing message' });
+                     });
                } catch (err) {
                   logger.error('Failed to parse JSON message', err);
                }

Also applies to: 1514-1554

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/opencode/implementation.ts` around lines 1394 -
1423, handleMessage is being invoked fire-and-forget (void handleMessage(msg))
while it now awaits long-running startServer/restartServer, causing races on
shared state (serverProc, managedSessions, managedPids) when Chrome sends
commands back-to-back; fix by serializing native-host command handling: replace
the fire-and-forget dispatch with a single-message queue or a sequential
processor that ensures only one handler runs at a time (e.g., a simple FIFO
Promise queue or an async loop) and have the parser enqueue messages instead of
calling handleMessage directly; ensure startServer, restartServer and stop
handlers are consumed from the queue in order and that errors are caught and
reported back before dequeuing the next message so status messages remain
ordered.
🧹 Nitpick comments (1)
.changeset/few-results-kneel.md (1)

5-5: ⚡ Quick win

Consider a more descriptive changelog entry.

The entry "fix: adjusted the native host process cleanup" is brief. Users reading the changelog would benefit from understanding what was adjusted and why. Consider adding details like "prevents terminating unmanaged processes" or "adds ownership tracking to avoid killing unrelated OpenCode servers."

📝 Example of a more descriptive entry
-fix: adjusted the native host process cleanup
+fix: improved native host process cleanup to only terminate managed or OpenCode-owned processes

or

-fix: adjusted the native host process cleanup
+fix: added ownership tracking to prevent native host from terminating unrelated OpenCode servers during cleanup
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/few-results-kneel.md at line 5, Replace the terse changelog
header "fix: adjusted the native host process cleanup" with a more descriptive
entry explaining what was changed and why; update the .changeset entry that
contains that exact header to something like "fix: avoid terminating unmanaged
native host processes by adding ownership tracking to native host cleanup" or
"fix: add ownership checks to native host process cleanup to prevent killing
unrelated OpenCode servers," and include one sentence describing the user-facing
impact and the root cause addressed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli/src/commands/opencode/implementation.ts`:
- Around line 742-757: The owner token is being persisted in cleartext at
NATIVE_HOST_OWNER_TOKEN_PATH and (per review) additionally logged; update
getOrCreateNativeHostOwnerToken to store/retrieve the token from the CLI's
secure token storage location (use the project's secure token API/utility or the
designated secure token path instead of
NATIVE_HOST_STATE_DIR/NATIVE_HOST_OWNER_TOKEN_PATH), remove any debugging or
logging that prints the raw token value (log only presence/creation events or a
redacted token), and apply the same changes to the duplicated logic referenced
around lines 1157-1160 so both code paths use secure storage and never emit the
token in logs.
- Around line 495-507: The persisted PID ownership from loadPersistedManagedPids
can outlive the original process and cause killProcessOnPort to kill unrelated
recycled PIDs; update the persistence/validation logic so ownedPersistedPids are
only trusted after validating a stable process identity or TTL. Change the
session-*.json format (used by loadPersistedManagedPids and cleanup) to store a
process startTime or unique process token (e.g., PID+startTime or a processUUID)
and when killProcessOnPort (and other callers) loads ownedPersistedPids, verify
each entry by checking the OS process start time or matching the stored process
token and/or rejecting entries older than a configurable TTL, and ensure cleanup
prunes stale session files when validation fails; adjust allowedOwnerToken
handling so only validated, live owners are considered.

---

Outside diff comments:
In `@packages/cli/src/commands/opencode/implementation.ts`:
- Around line 1394-1423: handleMessage is being invoked fire-and-forget (void
handleMessage(msg)) while it now awaits long-running startServer/restartServer,
causing races on shared state (serverProc, managedSessions, managedPids) when
Chrome sends commands back-to-back; fix by serializing native-host command
handling: replace the fire-and-forget dispatch with a single-message queue or a
sequential processor that ensures only one handler runs at a time (e.g., a
simple FIFO Promise queue or an async loop) and have the parser enqueue messages
instead of calling handleMessage directly; ensure startServer, restartServer and
stop handlers are consumed from the queue in order and that errors are caught
and reported back before dequeuing the next message so status messages remain
ordered.

---

Nitpick comments:
In @.changeset/few-results-kneel.md:
- Line 5: Replace the terse changelog header "fix: adjusted the native host
process cleanup" with a more descriptive entry explaining what was changed and
why; update the .changeset entry that contains that exact header to something
like "fix: avoid terminating unmanaged native host processes by adding ownership
tracking to native host cleanup" or "fix: add ownership checks to native host
process cleanup to prevent killing unrelated OpenCode servers," and include one
sentence describing the user-facing impact and the root cause addressed.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 80ef3537-a0a5-433c-ae9c-0ca90b77feaf

📥 Commits

Reviewing files that changed from the base of the PR and between 49f5942 and 68b26e9.

📒 Files selected for processing (2)
  • .changeset/few-results-kneel.md
  • packages/cli/src/commands/opencode/implementation.ts

Comment on lines +495 to +507
function killProcessOnPort(
port: number,
allowedPids: Set<number>,
logger?: { log: (msg: string, data?: any) => void; error: (msg: string, err?: any) => void },
options?: { allowUnmanagedOpencode?: boolean; allowedOwnerToken?: string },
): boolean {
const logInfo = logger?.log ?? ((msg: string) => { /* silent */ });
const logError = logger?.error ?? ((msg: string) => { /* silent */ });
const allowUnmanagedOpencode = options?.allowUnmanagedOpencode !== false;
const allowedOwnerToken = options?.allowedOwnerToken?.trim().toLowerCase();
const ownedPersistedPids = allowedOwnerToken
? loadPersistedManagedPids(allowedOwnerToken)
: new Set<number>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Restored PID ownership can outlive the original process.

loadPersistedManagedPids trusts every session-*.json entry for the owner token, but cleanup only removes metadata for sessions created by the current process. After a crash or reconnect, stale files survive indefinitely; once the OS recycles one of those PIDs, killProcessOnPort will treat an unrelated listener as owned and force-kill it. Bind persisted ownership to a stable process identity or TTL before reusing it.

Also applies to: 781-812, 1154-1160, 1475-1487

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/opencode/implementation.ts` around lines 495 - 507,
The persisted PID ownership from loadPersistedManagedPids can outlive the
original process and cause killProcessOnPort to kill unrelated recycled PIDs;
update the persistence/validation logic so ownedPersistedPids are only trusted
after validating a stable process identity or TTL. Change the session-*.json
format (used by loadPersistedManagedPids and cleanup) to store a process
startTime or unique process token (e.g., PID+startTime or a processUUID) and
when killProcessOnPort (and other callers) loads ownedPersistedPids, verify each
entry by checking the OS process start time or matching the stored process token
and/or rejecting entries older than a configurable TTL, and ensure cleanup
prunes stale session files when validation fails; adjust allowedOwnerToken
handling so only validated, live owners are considered.

Comment on lines +742 to +757
function getOrCreateNativeHostOwnerToken(): string {
ensureDirectoryExists(NATIVE_HOST_STATE_DIR);
try {
if (fs.existsSync(NATIVE_HOST_OWNER_TOKEN_PATH)) {
const existing = fs.readFileSync(NATIVE_HOST_OWNER_TOKEN_PATH, 'utf8').trim();
if (existing) {
return existing;
}
}
} catch {
// fall through and recreate
}

const token = randomUUID();
fs.writeFileSync(NATIVE_HOST_OWNER_TOKEN_PATH, token, 'utf8');
return token;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Owner token is persisted and logged in cleartext.

This token is now the capability that decides which servers are treated as "owned", but the change writes it to owner-token.txt and echoes the raw value into the debug log. Please move it to the CLI's secure token storage path and stop logging the token itself.

As per coding guidelines "packages/{cli,core}/src/**/*.{ts,tsx}: Implement secure storage for token management".

Also applies to: 1157-1160

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/opencode/implementation.ts` around lines 742 - 757,
The owner token is being persisted in cleartext at NATIVE_HOST_OWNER_TOKEN_PATH
and (per review) additionally logged; update getOrCreateNativeHostOwnerToken to
store/retrieve the token from the CLI's secure token storage location (use the
project's secure token API/utility or the designated secure token path instead
of NATIVE_HOST_STATE_DIR/NATIVE_HOST_OWNER_TOKEN_PATH), remove any debugging or
logging that prints the raw token value (log only presence/creation events or a
redacted token), and apply the same changes to the duplicated logic referenced
around lines 1157-1160 so both code paths use secure storage and never emit the
token in logs.

@MihalyToth20 MihalyToth20 merged commit 15e0915 into main May 25, 2026
4 checks passed
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