Skip to content

Automatically open routed workspaces with isolated WARP sidecars - #249

Merged
flyingrobots merged 61 commits into
mainfrom
cycle/isolated-auto-open-workspaces
Sep 28, 2026
Merged

flyingrobots merged 61 commits into
mainfrom
cycle/isolated-auto-open-workspaces

Conversation

@flyingrobots

@flyingrobots flyingrobots commented Aug 29, 2026 •

Copy link
Copy Markdown
Owner

Background Context

Graft's daemon keeps a WARP graph per repository. Until now that graph lived in the source repository's own Git objects and refs, and a routed tool call could only run in a workspace the session had opened first.

The Problem

Writing graph state into source repositories mixes Graft's data with the user's history, and linked worktrees and separate sessions could share one working graph. Routed calls given an explicit cwd in an unopened worktree also failed instead of admitting it.

The fix

  • Separate graphs: production WARP state goes to private bare sidecar repositories under <graft root>/graphs, derived from GRAFT_ROOT_PATH and so ~/.graft/graphs while that is unset. Each sidecar is keyed by repository, worktree and actor, and the source repository's refs, objects, config and hooks are left alone.
  • Automatic admission: a routed daemon tool given a non-empty explicit cwd admits its canonical containing worktree, without changing the session's active binding.
  • The graph root: it is resolved to its real path where Graft first reads it, so a root reached through a symlink (macOS /tmp) works. Storage then refuses symlinks anywhere in its tree, and refuses roots that overlap the source worktree or its Git directory.
  • Monitor checkpoints: persistent-monitor checkpoints stay with the worktree sidecar that produced them.
  • Test entry points: test:local and test:watch are removed and release:surface-gate runs through pnpm test, so every supported Vitest entry point uses the copy-in, no-mount Docker harness.

The branch is merged with current main, including the bounded resident pool (residents are now keyed by worktree as well) and GRAFT_ROOT_PATH. The test suite no longer redirects HOME.

Validation

At adc9a1b1, pnpm lint and pnpm typecheck pass. A full local run passes 2,460 of 2,463 tests; the 3 failures are playback tests that time out locally on main too. A full run leaves the developer's ~/.graft unchanged. Sidecar tests cover symlinked roots resolving to one location, and a root swapped for a symlink after startup being refused.

Known limits:

  • Existing source-repository refs/warp state is not migrated.
  • Sidecar retention and pruning are follow-up work.
  • Windows permission behavior is not exercised.

Summary by CodeRabbit

  • New Features

    • WARP data is stored in isolated sidecar repositories, keeping indexing and session history separate from source repository data.
    • CLI commands can use a configured graph root and resolve requested paths relative to the canonical worktree.
    • Daemon access can automatically authorize eligible workspaces and revalidate authorization when repository identity changes.
  • Bug Fixes

    • Workspace history and monitoring now remain anchored to the correct worktree.
    • Tests now run in isolated Docker containers without a host-side fallback.

@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: 2cde25760b

ℹ️ 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 thread src/warp/sidecar.ts Outdated
@flyingrobots

Copy link
Copy Markdown
Owner Author

Code Lawyer Activity Summary

# Source Severity File Commit Outcome
1 Self and PR P0 src/warp/sidecar.ts 1b66de0 Made graphRoot mandatory and propagated the exact locator authority through every worker boundary; omitted-root RED now fails before storage access.
2 PR P1 src/warp/sidecar.ts 66b8aa6 Removed chmod of pre-existing graph directories; unsafe modes are rejected unchanged.
3 PR P1 src/mcp/persistent-monitor-runtime.ts b8cd85f Reset sidecar-scoped checkpoints when monitor fallback changes anchors; same-HEAD fallback now performs a full seed.
4 PR P2 src/cli/index-cmd.ts 8dac6d7 Canonicalized an existing requested cwd before index-path containment checks; symlink aliases index correctly.
5 PR P5 test/unit/scripts/strip-copied-git-remotes.test.ts 47152e8 Replaced incidental stderr silence with semantic scrub assertions under benign Git trace diagnostics.
6 PR P4 test/unit/release/docker-test-isolation.test.ts 048cd80 Replaced source-substring checks with exported unavailable-message and injected Docker Desktop launch behavior.
7 PR P5 test/unit/release/docker-test-isolation.test.ts — Rejected: weakening the exact Alpine Git revision assertion would violate the explicit SCR-02 hermetic-input contract.

Verification

  • Full copy-in Docker suite at implementation head 048cd80: 262 files, 2,085 tests passed in 137.49s.
  • Lint, typecheck, build, and origin/main...HEAD diff check passed.
  • The unique test image was deleted after the run.
  • All four inline review threads are answered and resolved; GraphQL pagination is complete.
  • Closure witness published at current head 4ff95b8.

CodeRabbit was rate-limited on the latest delta, so the merge gate remains locked pending a substantive current-head review.

@codex review please

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 4ff95b850f

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

…pen-workspaces

# Conflicts:
#	CHANGELOG.md
#	docs/method/backlog/dependency-dag.dot
#	docs/method/backlog/dependency-dag.svg
#	package.json
#	schemas/graft-structural-history.echo-package.json
#	src/mcp/daemon-server.ts
#	src/mcp/server.ts
#	src/mcp/warp-pool.ts
#	src/mcp/workspace-router-model.ts
#	src/mcp/workspace-router-runtime.ts
#	src/mcp/workspace-router.ts
#	test/helpers/daemon.ts
#	test/unit/helpers/mcp.test.ts
#	test/unit/mcp/warp-pool.test.ts
#	test/unit/mcp/workspace-binding.test.ts
Brings in #261 (GRAFT_ROOT_PATH), #260, #256 and #254.

Conflicts resolved:
- src/mcp/daemon-server.ts: keep main's injected env for the daemon root,
  socket, startedAt and incarnationId, and this branch's options-object
  InMemoryWarpPool with its graph root.
- vitest.config.ts: register both setup files for now; the next commits
  replace this branch's HOME-redirecting setup with main's GRAFT_ROOT_PATH one.
- CHANGELOG.md, docs/MCP.md: keep both sides' entries and paragraphs.
- docs/method/backlog/dependency-dag.{dot,svg}: regenerated with
  scripts/generate-backlog-dependency-dag.ts (bad-code 35).

Known at this commit: pnpm lint fails on src/warp/sidecar.ts reading
os.homedir, which main's new rule forbids; fixed by the next commit.
… the home directory

The default graph root is now <graft root>/graphs: GRAFT_ROOT_PATH/graphs, or
~/.graft/graphs while the variable is unset. createGraftServer and
startDaemonServer resolve it from their injected env, as #261 did for the
daemon root, so a host's GRAFT_ROOT_PATH decides it rather than the process's.
src/warp/sidecar.ts no longer reads os.homedir, which main's lint rule forbids.

RED (before this change): graft-root.test.ts failed three tests, the graph
root resolving to /srv/graft/.graft/graphs, to the redirected test HOME, and
os.homedir being called twice; the daemon test's sidecar was absent under the
injected root (ENOENT). GREEN: both files pass.
… temp directory

Removes test/setup-hermetic-env.ts and test/unit/helpers/hermetic-environment.test.ts
(b899e38, 8232fff). Main's test/setup-graft-root.ts now isolates every
per-user default, the WARP graph root included, through GRAFT_ROOT_PATH, and
graft-root.test.ts checks that HOME is left as the process received it.

The canonical-TMPDIR half of 8232fff is still needed: the suite's Graft root
and many helpers' graph roots are created under the temp directory, and
sidecar storage refuses a symlink-aliased root. Without it a full run failed
275 tests with "Refusing symlinked Graft graph storage path". It moves to its
own setup file, test/setup-canonical-tmpdir.ts, registered before the
Graft-root setup, with its test in test/unit/helpers/canonical-tmpdir.test.ts.

RED: with setup-graft-root registered before the HOME redirect,
"leaves HOME as the process received it" failed (/Users/james vs a
graft-test-home temp dir); canonical-tmpdir.test.ts failed both tests with
the canonical setup unregistered (config restored byte for byte, sha256
14dae289...). GREEN: both files pass.
… re-deriving a default

createDaemonSessionHost now carries the daemon's graph root to each session's
createGraftServer. Before, every daemon session resolved its own default graph
root from the env; since the previous commit that goes through graftRootPath,
so a daemon started with an explicit graftDir, socketPath and graphRoot but a
relative GRAFT_ROOT_PATH in its env refused every session.

RED: the new daemon-server test "gives its sessions the daemon's explicit
graph root instead of re-deriving a default" got HTTP 500 for initialize.
GREEN: daemon-server.test.ts passes, 15 of 15.
… user-facing docs

README, ADVANCED_GUIDE, ARCHITECTURE, docs/CLI.md, docs/SETUP.md and the
indexing invariant still named ~/.graft/graphs as the only location. Since the
graph root now derives from graftRootPath(), they say it moves with
GRAFT_ROOT_PATH and is ~/.graft/graphs only while that is unset.
Documentation only; no runtime change.
…_PATH, must be a canonical path

Sidecar storage refuses a graph root reached through a symlink. With the
default now under GRAFT_ROOT_PATH, a value spelled through /tmp or /var on
macOS is refused for graph storage although the daemon root accepts it; the
test suite needed test/setup-canonical-tmpdir.ts for exactly this reason.
…s by worktree too

The card, merged from main, says the pool holds four (repoId, writerId)
handles. On this branch each resident is keyed by (repoId, worktreeId,
writerId); the card now says so.
…test replaces

project-root-resolution.test.ts gives createGraftServer an explicit env, and
server.test.ts starts its stdio child with only the SDK's default environment
plus its own. Neither carried GRAFT_ROOT_PATH, so once the WARP graph root
derived from the injected env (and HOME was no longer redirected) both opened
sidecars under the developer's real ~/.graft/graphs.

RED (first failure, full run at 533e8dc's successor 3d8bd4e..08d81fd, one
run only, not replayed because replaying writes into the real home): the run
added ~/.graft/graphs/graft-root-test-{huwajh,pqjord}--* and
graft-mcp-stdio-zsicot--*. GREEN: both files pass; the next full run's
listing comparison is the check.
…where it is first read, and keep refusing symlinks after that
@flyingrobots

Copy link
Copy Markdown
Owner Author

Code Lawyer Activity Summary

origin/main is merged into this branch twice: at 59438285 (15 files conflicted, chiefly src/mcp/warp-pool.ts, where main's bounded resident pool now opens this branch's per-worktree sidecars) and at fe03d850 (for #260, #256, #254 and #261). The PR's unreleased 0.13.0 CHANGELOG entries are now under Unreleased, since main shipped 0.13.0 and 0.14.0 without them; its v0.13.0 release notes in docs/releases and docs/method/releases still need the same move. The description above is rewritten to match.

# Source Severity Commit Outcome
1 Self P1 350d4f70 Fixed. The default graph root derives from graftRootPath(); nothing in the branch reads the home directory.
2 Self P1 aa6a158a Fixed. Two tests that replace a server's environment dropped GRAFT_ROOT_PATH and wrote into the real ~/.graft/graphs.
3 Self P2 533e8dcb Fixed. The suite no longer redirects HOME; GRAFT_ROOT_PATH isolates it.
4 Self P2 adc9a1b1 Fixed. A graph root reached through a symlink is resolved to its real path where it is first read, instead of refused; later steps still refuse symlinks.
5 Self P3 7c820e4b Fixed. Daemon sessions use the daemon's resolved graph root instead of re-deriving a default.
6 Self P3 8232fff6 Fixed. The test temp directory is canonicalized, so paths the tests build match the real paths Graft reports on macOS.
7 Self P3 b2757cf2 Fixed. A failed automatic authorization discards the cached routed binding.
8 Self P4 472f029d, 947d3fba, 08d81fd9, 4ce0c647 Fixed. The sidecar-location docs, the resident-pool packet and card, and CONTRIBUTING match the branch.

Local gates at adc9a1b1: pnpm lint and pnpm typecheck pass; a full run passes 2,460 of 2,463 tests, the 3 failures being playback tests that time out locally on main too.

@flyingrobots
flyingrobots merged commit 19d8452 into main Sep 28, 2026
3 checks passed
@flyingrobots
flyingrobots deleted the cycle/isolated-auto-open-workspaces branch September 28, 2026 08:55
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