Skip to content

fix(spawn): resolve a relative workdir against the session project - #72

Open
dhaern wants to merge 1 commit into
shekohex:mainfrom
dhaern:fix/spawn-workdir
Open

dhaern wants to merge 1 commit into
shekohex:mainfrom
dhaern:fix/spawn-workdir

Conversation

@dhaern

@dhaern dhaern commented Oct 5, 2026

Copy link
Copy Markdown

Summary

pty_spawn ignores the project directory of the session. A workdir is used exactly as typed, and when it is left out the PTY starts in the directory of the host process. In V2 the tool context has no directory at all. The directory permission check also receives the raw string, so it answers wrongly in both directions (see Cause).

In plain terms: when an agent asks for a terminal in src, the plugin does not know which project src belongs to. It can start the terminal in the wrong place, refuse a folder that is inside the project, or let through a path that climbs out of it. Now every working directory is resolved against the session's project first.

Cause

  • execute() passed args.workdir straight to manager.spawn(), which falls back to process.cwd() when it is undefined (session-lifecycle.ts:59).
  • checkWorkdirPermission() got the same raw string. V1PermissionAuthorizer.checkWorkdir decides with workdir.startsWith(project), so a relative src never matches the absolute project path, and /project/../../etc does.
  • registerV2Tools() handed the host's tool context to each tool unchanged. In V2 that context has no directory; the project is on ctx.location.directory. There was nothing to resolve against.

I called ptySpawn.execute with manager.spawn stubbed, external_directory: "deny" and the project at /tmp/proj:

workdir main this PR
src denied allowed, cwd /tmp/proj/src
/tmp/proj/../../etc allowed, cwd /tmp/proj/../../etc denied
/tmp/proj/src allowed allowed
omitted allowed, cwd undefined (host cwd) allowed, cwd /tmp/proj

Changes

  • spawn.ts: workdir = resolve(ctx.directory ?? '', args.workdir ?? ''). The permission check (still only when a workdir was given) and manager.spawn both receive the resolved absolute path.
  • v2/index.ts: read location.directory before registering the tools and pass it to registerV2Tools. The block only moves up.
  • v2/tools.ts: registerV2Tools(draft, directory?) fills directory into the context it hands to each tool. A directory the host already provides wins.

Behavior changes

  • A relative workdir is relative to the session project, not to the host process.
  • Omitting workdir starts the PTY in the session project instead of the host's current directory. Where the two are the same directory, nothing changes.
  • With permission.external_directory: "deny", a workdir inside the project is no longer refused for being relative, and one that climbs out with .. is now refused.

Validation

Two new tests: test/pty-tools.test.ts checks that a relative workdir is resolved before the permission check and the spawn, and test/v2.test.ts checks that the project directory reaches a tool context that has none. One existing test changes: the minimal spawn case now expects the context directory as workdir instead of undefined.

With src reset to main and the tests from this branch, those three tests fail. Replacing the resolve(...) with the raw args.workdir fails the two pty-tools tests, and removing the injection in tools.ts fails the V2 test.

bun test over test/*.test.ts, without the npm-pack and live suites, gives 186 passing. bun run typecheck, bun run lint and bunx biome format . are clean. The CI workflow passes on this commit in my fork, including the Playwright e2e. I did not run it against a live V2 host.

Diff

Production: +5 lines (+15/-10) in src/plugin/pty/tools/spawn.ts, src/v2/index.ts and src/v2/tools.ts.
Tests: +56 lines (+59/-3) in test/pty-tools.test.ts and test/v2.test.ts.

Default spawns to the session project and inject the V2 plugin location into tool contexts. Relative workdirs now resolve against the project; explicit directory permission checks receive the resolved absolute path.
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