Skip to content

ci(host-filesystem): prove host-install slice on Ubuntu, macOS, and Windows - #787

Merged
ScriptedAlchemy merged 18 commits into
mainfrom
cursor/host-install-os-matrix-e93a
Sep 12, 2026
Merged

ci(host-filesystem): prove host-install slice on Ubuntu, macOS, and Windows#787
ScriptedAlchemy merged 18 commits into
mainfrom
cursor/host-install-os-matrix-e93a

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Why

A GPT 6 Pro review of main treated filesystem/process portability as Ubuntu-only, while dev --install-host already has Windows junction publish, atomic rename/rollback, ownership manifests, path casing, and per-OS host config locations. The package is platform-generic (no os field). Cloning the whole Verify/examples/release suite onto three OSes would make every PR much slower.

What this does

Adds a three-cell hosted job, not a Node × OS product:

  • ubuntu-latest, macos-latest, windows-latest
  • Node 22.19 (engines floor)
  • pnpm build once, then pnpm test:host-filesystem via rstest.host-filesystem.config.ts

The slice is only the tests that can diverge by OS:

  • dev-host-install.test.ts (Claude/Codex CLI legs skip; the Unix-socket Codex app-server fake is it.skip on win32)
  • dev-host-install-manager.test.ts (rollback / stale ownership)
  • install.test.ts / uninstall.test.ts (receipt ownership)
  • durable-fs.test.ts (atomic publish, Windows directory fsync)
  • internal-child-resolution-policy.test.ts (packaged child resolution, fix(dev): fail closed when packaged internal child executables are missing #769)
  • npm-cli-resolution.test.ts (Windows official + Unix/nvm + PATH + split-prefix/pnpm npm_execpath)
  • packed-install-bin.test.ts (packaged installer bin from a consumer cwd)
  • rstest-worker-isolation.test.ts (canonical TMPDIR, process-identity roots)

Existing jobs stay Ubuntu-only: Verify, examples, release-gates, and the pinned-CLI host-install-proofs job.

GPT 6 Pro FIX after 6e270b97 (this head 89377e85)

Hosted Windows failed coordinator rebuild with AB7100 EPERM: operation not permitted, fsync. The config path in that diagnostic is failureDiagnostics() attribution, not the synced file.

Root cause: EpochStore #syncPath opened regular files read-only ('r'). Windows FlushFileBuffers requires write-capable access.

This tip implements the requested FIX and reverses the earlier file-EPERM swallow (a377fbb8 / 2b0875cb):

  • #syncPath opens existing regular files with non-truncating 'r+' when this.#platform === 'win32'; directories stay 'r'.
  • Directory-only FlushFileBuffers EACCES/EINVAL/EPERM tolerance is restored. Regular-file sync failures stay fatal and refuse publication (previous active epoch + staging cleanup).
  • Native Playground catalog staging no longer swallows file EPERM (that handle is already write-capable via 'wx').
  • durable-fs.ts is unchanged: it already refuses file EPERM, and production syncPath is directory-only (staging files use 'wx').
  • dev-host-install.test.ts still requires artifact.state === 'active' and artifact.available before attach.

GPT 6 Pro FIX after a8163c12 (kept)

  1. Nearest existing ancestor before containment. Missing paths walk to the nearest on-disk ancestor, realpath that ancestor, append the missing suffix, then check containment. Existing inputs still realpath first. Dangling leaf under an escaping symlink fails closed.
  2. Windows worker-root invocation identity. Hash temporaryRoot + workerId + cwd + pid. Unix under /tmp; Windows under host TEMP.
  3. Hosted three OS cells. Host filesystem ubuntu / macos / windows are green on 89377e85. Still draft pending Zack / GPT 6 Pro re-review.
  4. Residual risk. Slice is not a Node × OS product.

Follow-up on the first CI head (66f2a777) — kept

  • macOS: rstestWorkerRoot() publishes a realpath as TMPDIR.
  • Windows: dedicated rstest.host-filesystem.config.ts so this slice does not build examples/rsc-agent-runtime.

Out of scope

  • No merge. Zack will re-review this head with GPT 6 Pro before any merge.
  • Version Packages PR Version Packages #411 is untouched.
  • The example's Windows hook/index.js vs hook\\index.js mismatch is not claimed fixed; this slice simply no longer builds it.

Verification

Local Linux (agent VM, 89377e85)

node=v22.22.2 platform=linux
pnpm --filter agent-bundle build                         OK
rstest unit: epoch-store + native-playground             91 passed
  (win32 r+ open + bytes; file EPERM refuses publication;
   catalog file EPERM fatal; directory EPERM still tolerated)
AGENT_BUNDLE_PACKAGE_PREBUILT=1 AGENT_BUNDLE_WORKBENCH_PREBUILT=1
pnpm test:host-filesystem                               106 passed / 2 skipped
pnpm lint                                               OK (1508 files)

Hosted (89377e85 CI run 34676060514)

  • Host filesystem ubuntu / macos / windows: SUCCESS
  • Docs site / Changeset: SUCCESS
  • Verify (fast Node 24/26, integration-1/2) / Examples check / Release gates: SUCCESS
  • Host install proofs / RSC runtime micro-eval: SUCCESS

Windows job: https://github.com/ScriptedAlchemy/agent-bundle/actions/runs/34676060514/job/103506505809

Changeset: .changeset/host-install-windows-path-identity.md.

Open in Web Open in Cursor 

Keep Verify, examples, release-gates, and pinned-CLI host-install proofs
on ubuntu-latest. Add a three-cell matrix (ubuntu/macOS/Windows, Node
22.19) that builds once and runs only the host-install/filesystem tests
that can diverge by OS.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
@changeset-bot

changeset-bot Bot commented Sep 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 89377e8

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

This PR includes changesets to release 2 packages
Name Type
agent-bundle Patch
create-agent-bundle 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

@pkg-pr-new

pkg-pr-new Bot commented Sep 12, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@787
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@787
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/rsc-markdown-stream@787
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@787

commit: 89377e8

cursoragent and others added 17 commits September 12, 2026 03:12
…slice

macOS compared lexical /tmp fixtures to realpath /private/tmp results.
Windows died in rstest.config.ts while building the Workbench e2e
example this slice never uses. Give the host-filesystem job its own
config and publish a realpath worker temp root.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
…aunch

Judge project paths by on-disk identity so Windows 8.3 aliases and native
config paths no longer raise AB7001. Tolerate directory FlushFileBuffers
EPERM, replace development-host junctions by moving the previous pointer
aside, and launch packed npm/framework bins through Node entrypoints with
HOME plus USERPROFILE isolation.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
The packed-install-bin harness now checks Node's sibling npm, the
official node_modules/npm path, and nvm's lib/node_modules layout
before falling back to createRequire.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Preserve ENOENT-tolerant lexical canonicalization for not-yet-built
hook/payload paths. Resolve npm-cli.js from official, nvm, and PATH
layouts without createRequire. Report POSIX-only install tests as
skipped on Windows, and treat file-parent state roots as unproven
on Windows EEXIST.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
…junctions

Windows cannot follow pnpm's relative store links through a node_modules
junction, so the host-install fixture build failed to resolve
@modelcontextprotocol/server. Build that fixture under the package on
Windows so walk-up finds the real tree.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Launch prepack's npm pack dry-run through process.execPath and a discovered
npm-cli.js so hosted Windows (pnpm Node, no spawnable npm) no longer fails
AB5000. Missing project paths resolve the nearest existing ancestor so
escaping symlink leaves fail closed without throwing ENOENT. Windows
directory publish uses absolute junction targets; worker roots hash cwd+pid
on every OS; receipt-write rollback no longer depends on chmod.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
…ilure

A file at the receipts store blocked mkdir before host CLI verbs ran, so
rollback never issued uninstall/remove. The receipt file path as a
directory lets the host verbs succeed and then fails the atomic rename.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
…table

Occupying the receipt path broke the pre-write read. Spying
writeStoredInstallReceipt fails after host verbs on every OS, including
Windows where chmod 0555 is a no-op.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Compare packed-install-bin bundleRoot to the realpath npm root (8.3 vs
long). Publish Windows generation junctions at realpath targets, attach
the Cursor destination the manager actually installed, and junction
realpath compile deps into the Windows host-install fixture so a
coordinator rebuild can resolve them.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
A successful coordinator rebuild now treats the epoch it just built as
active so host-install attaches when the prepare snapshot digest and
compiler project revision disagree. Source snapshots record the same
identity path createProjectContext uses.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Share resolvedProjectPath between snapshot inputs and createProjectContext
so a Windows path.resolve spelling cannot fail the coordinator rebuild
with AB7101 and skip artifact.available.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Nearest-ancestor containment still judges escaping dangling leaves, but
a deleted configuration file or hashed source input must exist after
that gate. Prebuilt hook and payload paths stay allowed to be missing.
The coordinator host-install test now asserts an active epoch and
artifact.available before attach.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Hosted Windows FlushFileBuffers can fail on newly written epoch files
after a successful compile (AB7100). Keep the bytes and publish the
epoch instead of dropping artifact.available.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Coordinator rebuilds publish a native Playground catalog snapshot after
epoch sync. Hosted Windows FlushFileBuffers can EPERM that newly written
file the same way it EPERMs staged epoch files, wrapping as AB7100 and
leaving Cursor host-install unattached.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
File and directory FlushFileBuffers errors share one Windows tolerance
path, so the directory argument tripped no-unused-vars on Docs CI.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
File and directory FlushFileBuffers errors share one Windows tolerance
path, so the flag is unused. Call sites still pass true for directories;
underscore it for rslint instead of dropping the argument.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Hosted Windows FlushFileBuffers refuses read-only regular-file handles
(AB7100). EpochStore now opens existing files with non-truncating r+ on
win32, keeps directory opens read-only, and restores directory-only
EPERM tolerance so a genuine file sync failure still refuses publication.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
@ScriptedAlchemy
ScriptedAlchemy marked this pull request as ready for review September 12, 2026 06:17
@ScriptedAlchemy
ScriptedAlchemy merged commit 8a606e6 into main Sep 12, 2026
19 checks passed
@ScriptedAlchemy
ScriptedAlchemy deleted the cursor/host-install-os-matrix-e93a branch September 12, 2026 06:17
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 12, 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-12T06:23:56.306808Z 89377e8 Draft marked ready
ℹ️ 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: 89377e8590

ℹ️ 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".

Comment on lines +283 to +285
} catch (error) {
if (!isErrno(error, 'ENOENT')) throw error;
cursor = parent;

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 Reject dangling symlink ancestors before containment

When a missing model or prebuilt path is below a dangling symlink whose target is outside the project, realpathSync() returns ENOENT for both the full path and the symlink parent, so this loop skips over the symlink and reconstructs an apparently in-project lexical path from the project root. The containment check therefore accepts the escape, and the original model path can resolve outside the project if the target materializes before it is consumed; inspect and resolve symlink components rather than treating every ENOENT parent as nonexistent.

Useful? React with 👍 / 👎.

Comment thread docs/local-ci.md
Comment on lines +171 to +174
- `durable-fs.test.ts` (atomic publish, Windows directory fsync)
- `internal-child-resolution-policy.test.ts` (packaged child resolution, #769)
- `packed-install-bin.test.ts` (packaged installer bin from a consumer cwd)
- `rstest-worker-isolation.test.ts` (canonical TMPDIR; macOS `/tmp` → `/private/tmp`)

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 Include the npm CLI test in the documented slice

This list says it enumerates the tests run by pnpm test:host-filesystem, but it omits npm-cli-resolution.test.ts, which is included by rstest.host-filesystem.config.ts:29. Add that test to the list so the local-CI documentation matches the actual hosted matrix coverage.

AGENTS.md reference: AGENTS.md:L96-L99

Useful? React with 👍 / 👎.

"agent-bundle": patch
---

On Windows, `createProjectContext` now judges existing project paths by on-disk identity so 8.3 aliases and native config paths no longer raise `AB7001`; missing paths resolve the nearest existing ancestor (so a dangling leaf under an escaping symlink still fails closed) instead of throwing `ENOENT`. After that containment gate, the configuration file and every recorded source input must still exist (`ENOENT`); prebuilt hook and payload paths that are allowed not to exist yet stay valid. Source snapshots record the same identity path `createProjectContext` uses, so a development rebuild no longer fails with `AB7101` when `path.resolve` spelling differs from the realpath project identity, and a successful `agent-bundle dev` rebuild publishes `artifact.available` from the epoch it just built so host-install attaches even when a prepare snapshot digest and compiler project revision disagree. `agent-bundle prepack` launches npm's `npm-cli.js` through `process.execPath` and ignores a pnpm `npm_execpath`. Development host install publishes Windows directory junctions with absolute realpath targets and replaces an existing junction by moving the previous pointer aside; development epoch publication opens existing regular files with write-capable non-truncating `r+` on Windows before `fsync` so FlushFileBuffers is not refused as `EPERM` (`AB7100`), while directory FlushFileBuffers `EACCES`/`EINVAL`/`EPERM` stays best-effort and genuine file-sync failures still refuse publication; and a declared state root whose parent is a file records as unproven (`EEXIST`) instead of failing install. (#787)

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 Rewrite the changeset as an imperative user-facing summary

The changeset is a declarative, implementation-heavy account beginning with “now judges” and detailing internal flags, fsync behavior, and error handling, rather than an imperative release summary. Rewrite it in imperative voice around the user-visible commands and behavior while retaining the relevant diagnostic codes and (#787) suffix.

AGENTS.md reference: AGENTS.md:L124-L125

Useful? React with 👍 / 👎.

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.

2 participants