Skip to content

fix(coding-agent): create the win32 internal RPC socket dir recursively (fixes #1370) - #1420

Open
MoerAI wants to merge 2 commits into
code-yeongyu:mainfrom
MoerAI:fix/1370-rpc-internal-socket-mkdir
Open

fix(coding-agent): create the win32 internal RPC socket dir recursively (fixes #1370)#1420
MoerAI wants to merge 2 commits into
code-yeongyu:mainfrom
MoerAI:fix/1370-rpc-internal-socket-mkdir

Conversation

@MoerAI

@MoerAI MoerAI commented Sep 6, 2026

Copy link
Copy Markdown

Summary

On win32 the RPC host supervisor created its internal socket directory with mkdir(..., { recursive: false }) under <agentDir>/rpc-host-daemon. ensureHost() creates that parent before spawning, but a direct --internal-rpc-host-supervisor launch does not, so on a fresh Windows profile the supervisor died during bootstrap.

This makes the win32 branch create the directory recursively.

Addresses stage 1 of #1370.

Root cause

runHostSupervisor() calls createInternalSocketPath(paths.dir) with paths.dir = <agentDir>/rpc-host-daemon (host-lifecycle.ts). The win32 branch then does:

const dir = join(baseDir, `internal-${randomUUID()}`);
await mkdir(dir, { recursive: false, mode: 0o700 });

recursive: false requires rpc-host-daemon to exist. The posix branch is unaffected because it roots the directory in tmpdir(), which always exists — which is why a fresh profile works on macOS but not on Windows.

Changes

File Change
packages/coding-agent/src/modes/rpc/host-lifecycle.ts win32 internal socket dir is created with recursive: true; createInternalSocketPath is exported and takes an injectable platform, mirroring spawnableChildLaunch in the same module
packages/coding-agent/test/rpc-host-lifecycle.test.ts Regression coverage for the win32 bootstrap plus a guard that the posix branch still roots in the OS temp dir
packages/coding-agent/CHANGELOG.md [Unreleased] > Fixed entry

The posix branch is untouched. The injectable platform is what makes the win32 bootstrap testable from a non-Windows host; the same pattern already exists in this file for spawnableChildLaunch(launch, platform).

Reproduction (before fix)

$ bun run --cwd packages/coding-agent test test/rpc-host-lifecycle.test.ts -t createInternalSocketPath

 FAIL  test/rpc-host-lifecycle.test.ts > createInternalSocketPath
       > creates the win32 internal directory when rpc-host-daemon does not exist yet
Error: ENOENT: no such file or directory, mkdir
  '/var/folders/.../senpi-hlc-win32-tWVPDT/rpc-host-daemon/internal-f7839c6e-...'
 ❯ createInternalSocketPath src/modes/rpc/host-lifecycle.ts:111:3
    110|   const dir = join(baseDir, `internal-${randomUUID()}`);
    111|   await mkdir(dir, { recursive: false, mode: 0o700 });

 Tests  1 failed | 1 passed | 29 skipped (31)

That is the same ENOENT ... mkdir '<agentDir>\rpc-host-daemon\internal-<uuid>' reported in the issue.

Verification (after fix)

$ bun run --cwd packages/coding-agent test test/rpc-host-lifecycle.test.ts -t createInternalSocketPath
 Test Files  1 passed (1)
      Tests  2 passed | 29 skipped (31)

$ bun run check
CHECK_EXIT=0

Real-CLI QA (.agents/skills/senpi-qa), each asserting ~/.senpi/agent/auth.json is unchanged:

rpc-drive.mjs  --self-test : 4/4 passed
mock-loop.mjs  --with-tool : 4/4 passed
cli-smoke.mjs  --self-test : 8/8 passed

Evidence captured under local-ignore/qa-evidence/20260907-1370-rpc-internal-socket-mkdir/ (gitignored).

Test

  • Regression tests: packages/coding-agent/test/rpc-host-lifecycle.test.ts (win32 bootstrap + posix guard)
  • Static gate: bun run check — clean

Pre-existing failures, not caused by this change

test/rpc-host-lifecycle.test.ts > ensureHost-spawned host lifecycle has 6 failures on this machine (RPC socket host exited with code 1 before answering get_protocol_info). They reproduce identically with host-lifecycle.ts and rpc-host-lifecycle.test.ts restored verbatim from origin/main:

# origin/main, pristine
 Tests  6 failed | 1 passed | 22 skipped (29)

Remaining work on #1370 (stage 2)

Stage 2 of the issue — ENOENT ... open '<agentDir>/rpc/rpc.sock.secret' — is deliberately not addressed here. It comes from the unguarded read in runHostSupervisor():

const publicSecret =
  process.platform === "win32" ? await readSocketSecret(socketSecretPath(publicSocket)) : undefined;

The public secret is written by ensureHost() (host-ensure.ts, createSocketSecret(socketSecretPath(socket)) before the spawn), and prepareSocketPath() returns early on win32, so a direct supervisor launch has nobody to create it. Making the supervisor create or ensureSocketSecret() it would change the bootstrap contract from "requires a caller-created public secret" to "self-provisions one", which is your call rather than a mechanical fix. Happy to follow up in a separate PR once you say which semantics you want.


Summary by cubic

Fixes the Windows RPC host supervisor crashing on fresh profiles when launched directly via --internal-rpc-host-supervisor, because the internal socket directory under <agentDir>/rpc-host-daemon is now created recursively.

  • The win32 branch previously used mkdir with recursive: false, which required the parent to already exist; ensureHost() created it, but a direct supervisor launch did not, so the mkdir failed with ENOENT.
  • createInternalSocketPath is now exported with an injectable platform so the win32 path is testable from any OS, mirroring the existing spawnableChildLaunch pattern.
  • Regression coverage lives in test/suite/regressions/1370-rpc-internal-socket-mkdir.test.ts, per test/AGENTS.md; it covers the win32 bootstrap and guards that the posix branch stays rooted in the OS temp dir.
  • The behavior change is recorded in the changelog, docs/rpc.md, and src/modes/rpc/changes.md.
  • The posix branch is unchanged; stage 2 of Windows: RPC host supervisor crashes during bootstrap (non-recursive mkdir for rpc-host-daemon, then ENOENT read of rpc.sock.secret) #1370 (public socket secret on direct launch) is not addressed here.

Written for commit f9cf4dd. Summary will update on new commits.

Review in cubic

…ly (fixes code-yeongyu#1370)

runHostSupervisor() calls createInternalSocketPath(paths.dir) with paths.dir set to
<agentDir>/rpc-host-daemon. On win32 that directory was created with
mkdir(..., { recursive: false }), which requires rpc-host-daemon to already exist.

ensureHost() creates it before spawning, but a direct
'--internal-rpc-host-supervisor' launch does not, so on a fresh Windows profile
the supervisor died during bootstrap with
ENOENT: no such file or directory, mkdir '<agentDir>\\rpc-host-daemon\\internal-<uuid>'.

The win32 branch now creates the directory recursively. createInternalSocketPath
is exported and takes an injectable platform, mirroring spawnableChildLaunch in
the same module, so the win32 bootstrap path is covered from any host. The posix
branch is unchanged: it roots the directory in the OS temp dir, which always exists.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-06T18:13:58.893048Z 6697b13 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6697b134b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (platform === "win32") {
const dir = join(baseDir, `internal-${randomUUID()}`);
await mkdir(dir, { recursive: false, mode: 0o700 });
await mkdir(dir, { recursive: true, mode: 0o700 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Record the RPC behavior change in its scoped docs

This changes fresh-profile startup behavior in host-lifecycle.ts, but the commit leaves both packages/coding-agent/src/modes/rpc/changes.md and the shared-host lifecycle section of docs/rpc.md untouched. Record the Windows bootstrap behavior in both files in this increment so the fork tracker and documented lifecycle contract remain aligned.

AGENTS.md reference: packages/coding-agent/src/modes/rpc/AGENTS.md:L52-L52

Useful? React with 👍 / 👎.

Comment on lines +476 to +477
// Regression coverage for https://github.com/code-yeongyu/senpi/issues/1370
describe("createInternalSocketPath", () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move the issue regression out of the legacy root cluster

Because this block is explicitly regression coverage for issue #1370, adding it to the legacy test/*.test.ts cluster violates the scoped test-placement rule. Move the coverage into a suitably named test/suite/regressions/1370-*.test.ts file rather than growing this root test file.

AGENTS.md reference: packages/coding-agent/test/AGENTS.md:L50-L50

Useful? React with 👍 / 👎.

…regressions

test/AGENTS.md scopes issue regressions to suite/regressions/<issue>-<slug>.test.ts and
says the legacy flat test/*.test.ts cluster must not grow, so the coverage moves out
of rpc-host-lifecycle.test.ts, which is restored to its upstream content.

src/modes/rpc/AGENTS.md also requires a behavior change to update changes.md and
docs/rpc.md in the same increment, so both now record the recursive win32 internal
socket directory and note that the public socket secret stays caller-provisioned.

Reported by Codex review on code-yeongyu#1420.
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