Repository navigation
fix(dev-lead): check out PR branches in an isolated worktree (#448) #449
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
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| #!/usr/bin/env bash | ||
| # pr-worktree.sh — check out a PR branch in an isolated git worktree. | ||
| # | ||
| # Why this exists | ||
| # --------------- | ||
| # dev-lead's prompt templates (prompts/dev-lead/*.md) and helper scripts | ||
| # (scripts/*) live in the SAME repository the agent operates on. The previous | ||
| # approach ran `gh pr checkout` in place, switching the agent's working tree to | ||
| # the PR branch. When that branch predated a prompt/script file, the file | ||
| # vanished mid-run, e.g.: | ||
| # | ||
| # scripts/dev-lead-fix-reviews.sh: line 51: | ||
| # prompts/dev-lead/on-mention.md: No such file or directory | ||
| # | ||
| # (See petry-projects/.github-private run 27077585464 and issue #448.) | ||
| # | ||
| # A worktree gives the PR branch its own directory, so the agent checkout — and | ||
| # everything under it — stays on the agent ref while the engine edits the PR. | ||
| # | ||
| # Usage | ||
| # ----- | ||
| # source "$(dirname "$0")/lib/pr-worktree.sh" | ||
| # PROMPTS_DIR="$(resolve_abs "$PROMPTS_DIR")" # pin BEFORE entering worktree | ||
| # checkout_pr_in_worktree "$PR_NUMBER" "$REPO" # cds into the worktree | ||
| # ... run engine, commit, push (now operating on the PR branch) ... | ||
| # # the worktree is removed automatically on exit (EXIT trap) | ||
| # | ||
| # IMPORTANT: resolve any path the agent reads after checkout (PROMPTS_DIR, and | ||
| # anything else relative to the original CWD) to an absolute path with | ||
| # resolve_abs BEFORE calling checkout_pr_in_worktree — the cd changes what | ||
| # relative paths resolve to. | ||
|
|
||
| # resolve_abs <path> — echo an absolute form of <path> when it exists, else the | ||
| # original string unchanged. Used to pin PROMPTS_DIR before we cd elsewhere. | ||
| resolve_abs() { | ||
| case "$1" in | ||
| /*) printf '%s\n' "$1" ;; | ||
| *) ( cd "$1" 2>/dev/null && pwd ) || printf '%s\n' "$1" ;; | ||
| esac | ||
| } | ||
|
|
||
| # Directory of the active PR worktree (empty when none). Tracked so the EXIT | ||
| # trap can tear it down, and so the cleanup is idempotent. | ||
| PR_WORKTREE_DIR="" | ||
| # The agent checkout to return to during cleanup; captured at checkout time. | ||
| PR_WORKTREE_AGENT_DIR="" | ||
|
|
||
| # checkout_pr_in_worktree <pr_number> <repo> | ||
| # Creates a temp worktree, checks the PR out inside it, and cds into it. | ||
| # Registers an EXIT trap that removes the worktree. | ||
| checkout_pr_in_worktree() { | ||
| local pr="$1" repo="$2" | ||
|
|
||
| PR_WORKTREE_AGENT_DIR="$(pwd)" | ||
|
|
||
| # mktemp -d gives us a private parent dir; the worktree leaf must NOT pre-exist | ||
| # because `git worktree add` refuses a populated target directory. | ||
| local parent | ||
| parent="$(mktemp -d "${TMPDIR:-/tmp}/dev-lead-wt-XXXXXX")" | ||
| PR_WORKTREE_DIR="${parent}/pr-${pr}" | ||
|
|
||
| # Tear the worktree down on exit (success or failure) so /tmp and the repo's | ||
| # worktree registry don't accumulate stale entries across runs. | ||
| trap cleanup_pr_worktree EXIT | ||
|
|
||
| # Start detached at the agent checkout's HEAD so `gh pr checkout` can create or | ||
| # switch to the PR branch inside the worktree without colliding with whatever | ||
| # branch the agent checkout already has out. | ||
| git worktree add --detach "$PR_WORKTREE_DIR" HEAD >/dev/null | ||
|
|
||
| cd "$PR_WORKTREE_DIR" || return 1 | ||
| gh pr checkout "$pr" --repo "$repo" | ||
| } | ||
|
|
||
| # cleanup_pr_worktree — return to the agent checkout and remove the worktree. | ||
| # Idempotent and safe to call from an EXIT trap or by hand. | ||
| cleanup_pr_worktree() { | ||
| [ -n "${PR_WORKTREE_DIR:-}" ] || return 0 | ||
| local wt="$PR_WORKTREE_DIR" parent | ||
| parent="$(dirname "$wt")" | ||
| PR_WORKTREE_DIR="" | ||
|
|
||
| cd "${PR_WORKTREE_AGENT_DIR:-/}" 2>/dev/null || true | ||
| git worktree remove --force "$wt" 2>/dev/null || true | ||
| # Best-effort: drop the mktemp parent and prune any dangling registry entry. | ||
| if [ -n "$parent" ] && [ "$parent" != "/" ] && [[ "$parent" == *"/dev-lead-wt-"* ]]; then | ||
| rm -rf "$parent" 2>/dev/null || true | ||
| fi | ||
| git worktree prune 2>/dev/null || true | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| #!/usr/bin/env bats | ||
| # Unit tests for scripts/lib/pr-worktree.sh | ||
| # | ||
| # Regression coverage for issue #448: checking out a PR branch must NOT remove | ||
| # the agent's own prompt/script files from the working tree. The library | ||
| # isolates the PR branch in a separate git worktree, so the agent checkout — | ||
| # and an absolute PROMPTS_DIR pinned to it — survives the branch switch. | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../.." && pwd)" | ||
| LIB="$SCRIPT_DIR/scripts/lib/pr-worktree.sh" | ||
|
|
||
| setup() { | ||
| STUB_BIN_DIR="$(mktemp -d)" | ||
| # gh stub: `gh pr checkout <n>` switches the worktree to the PR branch, | ||
| # mirroring the real command (which is what made the old prompt files vanish). | ||
| cat > "$STUB_BIN_DIR/gh" <<'GHEOF' | ||
| #!/usr/bin/env bash | ||
| case "$1 $2" in | ||
| "pr checkout") git checkout -q old ;; | ||
| *) echo "{}" ;; | ||
| esac | ||
| GHEOF | ||
| chmod +x "$STUB_BIN_DIR/gh" | ||
| export PATH="$STUB_BIN_DIR:$PATH" | ||
|
|
||
| # Repo with a `main` branch that has prompts/x.md and an `old` branch that | ||
| # predates it — the exact shape that broke run 27077585464. | ||
| AGENT_REPO="$(mktemp -d)" | ||
| git -C "$AGENT_REPO" init -q -b main | ||
| git -C "$AGENT_REPO" -c user.email=t@t -c user.name=t commit -q --allow-empty -m base | ||
| git -C "$AGENT_REPO" branch old | ||
| mkdir -p "$AGENT_REPO/prompts" | ||
| echo "PROMPT BODY" > "$AGENT_REPO/prompts/x.md" | ||
| git -C "$AGENT_REPO" add prompts/x.md | ||
| git -C "$AGENT_REPO" -c user.email=t@t -c user.name=t commit -q -m "add prompt" | ||
| } | ||
|
|
||
| teardown() { | ||
| rm -rf "$STUB_BIN_DIR" "$AGENT_REPO" | ||
| } | ||
|
|
||
| @test "resolve_abs: leaves an absolute path unchanged" { | ||
| source "$LIB" | ||
| run resolve_abs /etc | ||
| [ "$status" -eq 0 ] | ||
| [ "$output" = "/etc" ] | ||
| } | ||
|
|
||
| @test "resolve_abs: converts an existing relative path to absolute" { | ||
| source "$LIB" | ||
| cd "$AGENT_REPO" | ||
| run resolve_abs prompts | ||
| [ "$status" -eq 0 ] | ||
| [ "$output" = "$AGENT_REPO/prompts" ] | ||
| } | ||
|
|
||
| @test "resolve_abs: returns a non-existent path unchanged" { | ||
| source "$LIB" | ||
| run resolve_abs does/not/exist | ||
| [ "$status" -eq 0 ] | ||
| [ "$output" = "does/not/exist" ] | ||
| } | ||
|
|
||
| @test "checkout_pr_in_worktree: PR branch switch does not clobber the agent prompts" { | ||
| source "$LIB" | ||
| cd "$AGENT_REPO" | ||
| # Pin PROMPTS_DIR before entering the worktree, as the real scripts do. | ||
| local prompts; prompts="$(resolve_abs prompts)" | ||
|
|
||
| checkout_pr_in_worktree 42 owner/repo | ||
|
|
||
| # We are now inside the worktree, on the `old` branch where prompts/x.md | ||
| # never existed — proving the branch switch really happened here. | ||
| [ "$PWD" = "$PR_WORKTREE_DIR" ] | ||
| [ ! -e "$PWD/prompts/x.md" ] | ||
|
|
||
| # The agent checkout (and the absolute PROMPTS_DIR pointing at it) is intact: | ||
| # this is the guarantee that issue #448 broke. | ||
| [ -f "$prompts/x.md" ] | ||
| [ "$(cat "$prompts/x.md")" = "PROMPT BODY" ] | ||
|
|
||
| cleanup_pr_worktree | ||
| } | ||
|
|
||
| @test "cleanup_pr_worktree: removes the worktree and returns to the agent checkout" { | ||
| source "$LIB" | ||
| cd "$AGENT_REPO" | ||
|
|
||
| checkout_pr_in_worktree 42 owner/repo | ||
| local wt="$PR_WORKTREE_DIR" | ||
| [ -d "$wt" ] | ||
|
|
||
| cleanup_pr_worktree | ||
|
|
||
| [ "$PWD" = "$AGENT_REPO" ] | ||
| [ ! -d "$wt" ] | ||
| [ -z "$PR_WORKTREE_DIR" ] | ||
| # Registry no longer references the removed worktree. | ||
| run git -C "$AGENT_REPO" worktree list | ||
| [[ "$output" != *"$wt"* ]] | ||
| } | ||
|
|
||
| @test "cleanup_pr_worktree: is a no-op when no worktree is active" { | ||
| source "$LIB" | ||
| PR_WORKTREE_DIR="" | ||
| run cleanup_pr_worktree | ||
| [ "$status" -eq 0 ] | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.