Skip to content

ci: prevent the coding agent from working in a discarded worktree - #4835

Open
Behzad Mirkhanzadeh (behzad-mir) wants to merge 1 commit into
masterfrom
fix/agent-worktree-empty-pr
Open

ci: prevent the coding agent from working in a discarded worktree#4835
Behzad Mirkhanzadeh (behzad-mir) wants to merge 1 commit into
masterfrom
fix/agent-worktree-empty-pr

Conversation

@behzad-mir

@behzad-mir Behzad Mirkhanzadeh (behzad-mir) commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Problem

The Copilot coding agent has opened two pull requests with a detailed description and zero changed files#4756 and #4833. Both reported success. All checks were green.

Log forensics on the working directory of every tool call:

Run Working directory Files
33692935610 (#4833) .../session-state/<id>/files/worktrees/go-127-upgrade 0
32806937007 (#4756) worktree (29 of 87 tool calls) 0
32814359584 (fork #49) $GITHUB_WORKSPACE 46

The agent did real work — edits, make dockerfiles, go build, go vet, git add . — inside a git worktree. The commit/push step only sees $GITHUB_WORKSPACE, so all of it was discarded.

agents.md §6 mandates worktree use and declares the repo root read-only. That is correct for local Copilot CLI sessions, where one clone is shared between concurrent sessions. It is wrong for the cloud agent, which gets an ephemeral single-session container.

An earlier theory blamed the firewall. That was wrong: the successful fork run had 8 blocked domains, the failing run had 1 (telemetry).

Why instructions are not enough

The same agents.md was present for all three runs. It produced worktree behavior twice and non-worktree behavior once. The fork run succeeded by luck, not configuration — which is why validating the earlier fix on the fork proved nothing.

Fix

copilot-setup-steps.yml installs a git shim at /usr/local/bin/git. That path precedes /usr/bin in the runner PATH, so it intercepts every PATH-resolved git for the rest of the job, including the agent phase. It does not rely on $GITHUB_PATH/$GITHUB_ENV propagating.

  • git worktree add → exits 1 with a message pointing at $GITHUB_WORKSPACE
  • everything else → exec /usr/bin/git "$@"

Argument parsing skips global options that consume a value (-C, -c, --git-dir, ...). Without this, git -C <path> worktree add treats the path as the subcommand and bypasses the guard — a real hole found while testing the first version.

The step self-tests and fails setup if the shim does not resolve first, does not block all five worktree add spellings, or leaks a directory. It cannot silently end up unguarded.

Verified:

blocked: worktree add
         worktree add -b b
         -C <path> worktree add
         --no-pager worktree add
         -c a=b -C <path> worktree add
         --git-dir <path> worktree add

passes:  --version, status, worktree list, -C <path> worktree list,
         log, -c core.pager=cat diff,
         commit -m "worktree add stuff"   <- not a false positive

Also

  • .github/copilot-instructions.md — carve-out placed above the line that pulls in agents.md, so the agent knows where to work before it hits the block. Correctness no longer depends on it.
  • agents.md §6 / SKILL.md — scoped the worktree rule to local CLI sessions.

Not included

An earlier commit in this branch added an empty-PR detector. Removed — it caught the failure after the fact instead of preventing it.

Verification after merge

Re-run #4832. In the agent log, grep -c worktrees over tool.execution_start lines should be 0.

Copilot AI lite review requested due to automatic review settings September 2, 2026 23:15
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The newly added “MANDATORY” shell snippets should explicitly guard against an unset $GITHUB_WORKSPACE and ensure the base ref exists so the checks fail deterministically for the intended reasons.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR updates contributor/agent guidance to prevent GitHub Copilot’s cloud coding agent (running in GitHub Actions) from doing work in a discarded git worktree, which previously resulted in empty upgrade PRs despite substantial work being performed.

Changes:

  • Add an explicit exception in agents.md clarifying that the cloud coding agent must work directly in $GITHUB_WORKSPACE (no worktrees).
  • Add “working directory” and “non-empty branch” guardrails to the acn-go-version-bump skill to prevent claiming completion when no changes are actually committed.
File summaries
File Description
agents.md Documents the cloud-agent exception to the local worktree-only workflow so work isn’t lost outside $GITHUB_WORKSPACE.
.github/skills/acn-go-version-bump/SKILL.md Adds mandatory checks to ensure work happens in the correct directory and the branch contains real diffs before reporting done.
Review details

Suppressed comments (1)

.github/skills/acn-go-version-bump/SKILL.md:637

  • In the final “branch is non-empty” verification block, cd "$GITHUB_WORKSPACE" has the same failure mode as above (empty var → cd to $HOME), and origin/master may not exist in a shallow checkout. Add a guard and ensure the base ref is present before running the diffs, so the check fails for the right reason.
cd "$GITHUB_WORKSPACE"
git status --short
git diff --stat origin/master...HEAD
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

This has already caused two empty PRs (#4756, #4833). Verify before you start:

```bash
cd "$GITHUB_WORKSPACE" && pwd && git rev-parse --show-toplevel

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valid

@behzad-mir Behzad Mirkhanzadeh (behzad-mir) changed the title docs: stop cloud coding agent from working in a discarded worktree ci: prevent the coding agent from working in a discarded worktree Sep 2, 2026
The Copilot coding agent opened #4756 and #4833 with detailed
descriptions and zero changed files. Both reported success and CI was
green, so the failure was silent.

Log forensics on the working directory of every tool call:

  run 33692935610 (#4833)     worktree    ->  0 files
  run 32806937007 (#4756)     worktree    ->  0 files
  run 32814359584 (fork #49)  workspace   -> 46 files

The agent did real work -- edits, make dockerfiles, go build, go vet,
git add . -- inside a git worktree under session-state. Its commit/push
step only sees $GITHUB_WORKSPACE, so all of it was discarded.

agents.md section 6 mandates worktrees and declares the repo root
read-only. That is correct for local CLI sessions, where one clone is
shared across concurrent sessions, and wrong for the cloud agent, which
gets an ephemeral single-session container.

An earlier theory blamed the firewall. It was wrong: the successful fork
run had 8 blocked domains, the failing run had 1 (telemetry only).

Instructions alone are not a control here. The same agents.md was present
for all three runs and produced worktree behavior twice and workspace
behavior once, so the fork validation proved nothing.

copilot-setup-steps.yml therefore installs a git shim at
/usr/bin/git and /usr/local/bin/git, with the real binary preserved at
/usr/local/lib/git-guard/git.real. Installing at both entry points means
every PATH-resolved git for the rest of the job, including the agent
phase. It rejects 'worktree add' with a message redirecting to
$GITHUB_WORKSPACE and execs real git for everything else. Shadowing by
PATH position means it does not depend on $GITHUB_PATH or $GITHUB_ENV
propagating into that phase.

Argument parsing skips global options that consume a value (-C, -c,
--git-dir, ...). Without that, 'git -C <path> worktree add' treats the
path as the subcommand and bypasses the guard -- a real hole found while
testing the first version.

The step self-tests and fails setup if the shim does not resolve first,
does not block all five probes, or leaks a directory, so it cannot end up
silently unguarded. Verified on ubuntu-latest in run 33695179506:

  blocked  worktree add / add -b / -C <path> worktree add /
           --no-pager worktree add / -c a=b -C <path> worktree add /
           --git-dir <path> worktree add
  passes   --version, status, worktree list, -C <path> worktree list,
           log, -c core.pager=cat diff,
           commit -m 'worktree add stuff'   (not a false positive)

The documentation changes scope the worktree rule to local CLI sessions.
copilot-instructions.md carries the carve-out above the line that pulls
agents.md in, since that line is how the rule reached the cloud agent.
SKILL.md adds a working-directory check and forbids reporting completion
on an empty diff. None of these are load-bearing; correctness rests on
the shim.

Verification after merge: re-run #4832 and confirm the agent log has zero
tool.execution_start lines under a worktrees path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b043d9fe-4797-42bf-9482-c337444c5a6f

Hardening: the shim is installed at /usr/bin/git as well as
/usr/local/bin/git. A PATH-position-only shim was bypassable with an
explicit '/usr/bin/git worktree add', demonstrated locally. The real
binary is preserved at /usr/local/lib/git-guard/git.real, copied once
under a guard so a re-run cannot overwrite it with the shim.
/usr/lib/git-core/git is deliberately left untouched so git's internal
helper dispatch is unaffected.

Self-test now covers 8 spellings including both absolute paths.

@rbtr Evan Baker (rbtr) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

few nits


### Working Directory (MANDATORY — read before you edit anything)

**If you are the GitHub Copilot coding agent running on a GitHub Actions runner, do ALL work in `$GITHUB_WORKSPACE`. Do NOT create a git worktree.**

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we just say - "If $GITHUB_WORKSPACE is set, use it instead of worktree"


`agents.md` instructs local CLI sessions to work in a dedicated worktree because the local repo root is shared between concurrent sessions. That does **not** apply to you: the cloud runner is a single-session ephemeral container, and your commit/push step only sees `$GITHUB_WORKSPACE`. Work done in a worktree elsewhere on disk is **silently discarded**, producing a PR with a detailed description and zero files.

This has already caused two empty PRs (#4756, #4833). Verify before you start:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cut

This has already caused two empty PRs (#4756, #4833). Verify before you start:

```bash
cd "$GITHUB_WORKSPACE" && pwd && git rev-parse --show-toplevel

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valid

Comment on lines +39 to +40
# The coding agent has twice done all its work inside a git worktree
# outside $GITHUB_WORKSPACE (#4756, #4833). Only $GITHUB_WORKSPACE is

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cut, these should be stateless, portable instructions, not an RCA referencing PRs

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.

3 participants