-
Notifications
You must be signed in to change notification settings - Fork 0
[codex] fix team isolated worktree setup #177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { afterEach, expect, test } from "bun:test" | ||
| import { mkdir, readdir } from "fs/promises" | ||
| import { mkdir, readdir, rm } from "fs/promises" | ||
| import path from "path" | ||
| import team from "../../../../packages/guardrails/profile/plugins/team" | ||
| import { Instance } from "../../src/project/instance" | ||
|
|
@@ -567,3 +567,61 @@ test("team worktrees carry root node_modules into isolated write tasks", async ( | |
| expect(result).toContain("state: done") | ||
| expect(result).toContain("no_patch=true") | ||
| }) | ||
|
|
||
| test("team rejects a task directory outside the active worktree and removes provisional worktrees", async () => { | ||
| await using tmp = await tmpdir({ | ||
| git: true, | ||
| init: async (dir) => { | ||
| await Bun.write(path.join(dir, "README.md"), "# test\n") | ||
| await Bun.$`git add README.md`.cwd(dir).quiet() | ||
| await Bun.$`git commit -m "seed"`.cwd(dir).quiet() | ||
| }, | ||
| }) | ||
|
|
||
| const outside = path.join(path.dirname(tmp.path), `opencode-outside-${crypto.randomUUID()}`) | ||
| await mkdir(outside, { recursive: true }) | ||
|
|
||
| const plugin = await createPlugin(tmp.path, tmp.path, { | ||
| async create() { | ||
| throw new Error("session create should not run") | ||
| }, | ||
| async promptAsync() { | ||
| throw new Error("prompt should not run") | ||
| }, | ||
| }) | ||
|
|
||
| try { | ||
| await expect( | ||
| plugin.tool.team.execute( | ||
| { | ||
| strategy: "parallel", | ||
| limit: 1, | ||
| tasks: [ | ||
| { | ||
| id: "outside", | ||
| prompt: "write a file", | ||
| write: true, | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| sessionID: "ses_parent", | ||
| messageID: "msg_parent", | ||
| agent: "implement", | ||
| directory: outside, | ||
| worktree: tmp.path, | ||
| abort: AbortSignal.timeout(5000), | ||
| metadata() {}, | ||
| ask() { | ||
| return undefined as never | ||
| }, | ||
| }, | ||
| ), | ||
| ).rejects.toThrow("directory is outside worktree") | ||
|
|
||
| const list = await readdir(path.join(tmp.path, ".opencode", "team"), { withFileTypes: true }).catch(() => []) | ||
| expect(list.filter((item) => item.isDirectory()).length).toBe(0) | ||
|
Comment on lines
+571
to
+623
|
||
| } finally { | ||
| await rm(outside, { recursive: true, force: true }) | ||
| } | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yardadd()builds the worktree path underrealpath(dir)(cwd) but most callers (including thefinallycleanup andmerge()’s cleanup) passrepoRoot/dirthrough unchanged. If the repo is accessed via a symlinked path (common on macOS,/varvs/private/var, or user-created symlinks),yardrm(dir, item)computes itsbasefrom the symlink path and thewithin(base, next)check will fail fornext(created under the real path), causing cleanup to be skipped and leaving worktrees behind. Canonicalizedirinsideyardrm()(and use that canonicaldirfor thegit -Ccalls) or ensureyardadd()constructsbase/nextusing the same canonical path thatyardrm()will later use.