utils: add agent_worktree.py for isolated PR/branch checkouts - #3228
Open
doomedraven wants to merge 3 commits into
Open
doomedraven wants to merge 3 commits into
doomedraven wants to merge 3 commits into
Conversation
Reviewing a PR or reproducing a bug against another branch currently means checking it out in your working clone. Most clones carry uncommitted work, so that is either unsafe or requires manual git worktree plumbing: figuring out which fork the head branch lives in, fetching it under a sane name, setting upstream, and cleaning up afterwards. utils/agent_worktree.py wraps that: new --pr <id> resolve the head fork via gh, fetch, branch, check out new --branch <name> / --from <ref> list / path / update / remove / cleanup / info It is stdlib-only and repository-agnostic. Worktrees it creates are tagged with metadata inside the git admin directory, so nothing extra appears in git status and cleanup only ever touches its own worktrees. remove and cleanup refuse to discard uncommitted changes or unpushed commits without --force, and the main worktree can never be removed. tests/test_agent_worktree.py covers the CLI against throwaway local repositories; no network, credentials or CAPE configuration required. SKILLS.md gains sections on the local development environment, isolated checkouts, and codebase behaviours that cause silent failures.
Clones that follow the fork convention have origin pointing at the contributor's fork and upstream at the canonical repository. Two things broke there: * repo_slug() preferred origin, so 'gh pr view' was asked about the fork instead of the repository the PR was opened against. upstream now wins when it is configured; clones made straight from the canonical repo are unaffected because they have no upstream. * 'new --branch' fetched from origin only and failed outright if the branch lived upstream. It now tries origin, then upstream, then any other remote, and reports which one it used. A missing branch is now a clear error naming the remotes that were tried.
…mote
The removal guard compared HEAD against the tracked upstream only. A review
branch normally tracks the branch it will merge into while its commits are
pushed to a fork, so 'rev-list @{upstream}..HEAD' is non-zero even though
nothing is at risk, and cleanup refused to remove a worktree whose work was
safely published.
The guard now treats work as unpushed only when no remote-tracking ref
contains HEAD at all, which is the condition that actually matters.
doomedraven
added a commit
to doomedraven/capemon
that referenced
this pull request
Sep 14, 2026
Mirrors kevoreilly/CAPEv2#3228. The removal guard compared HEAD against the tracked upstream only, so a branch tracking 'upstream/capemon' while its commits were pushed to a fork looked unpushed and cleanup refused to remove it. Work now counts as unpushed only when no remote-tracking ref contains HEAD.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What
Adds
utils/agent_worktree.py, a wrapper aroundgit worktreefor reviewing PRs and testing branches without touching your working clone, plus the tests and documentation for it.Why
Checking out someone else's branch currently means either polluting your clone or doing the
git worktreeplumbing by hand: working out which fork the head branch lives in, fetching it under a name that will not collide, setting upstream, and remembering to tear it down. Most clones carry uncommitted work, so a straygit checkout/git stashloses it. This is a recurring hazard both for humans reviewing PRs and for automation (CI helpers, review bots, coding agents) that cannot know whether a clone is clean.Usage
--jsonon any command for scripted use.--repo PATHto operate on a repository other than the current directory.Behaviour worth reviewing
checkout,reset,stashorclean.removerefuses the main worktree outright.agent-meta.jsonwritten into.git/worktrees/<id>/, i.e. inside the git admin directory, sogit statusin the worktree stays empty and a hand-madegit worktree addis never touched bycleanup.removeandcleanupbail on uncommitted changes or unpushed commits;cleanupskips those and prints the reason.--forceoverrides.new --prasksghfor the head repository. If a matching remote exists it fetches from it and sets upstream; if not, it fetches straight from the fork URL into a private ref namespace and warns that no upstream was configured.~/.cache/agent-worktrees/<repo>/<name>, overridable with--base-dir,--pathor$AGENT_WORKTREE_DIR. Deliberately not/tmp, which gets reaped.Implementation notes: standard library only, no new dependencies, and nothing CAPE-specific — it works in any git repository.
ghis needed only for--pr.Tests
tests/test_agent_worktree.py— 17 tests building throwaway local repositories intmp_path. No network, no GitHub credentials, no CAPE configuration. The--prpath is the only uncovered branch since it needs a live API.ruff checkandblack --check(line-length 132) are clean on both new files.Docs
SKILLS.mdgains three sections under Development Guides:ruffas the fast gate,black/ruff formatat 132 columns, andpytest -p no:warningsto stop several hundred third-party deprecation warnings from burying results.Dictionary.__getattr__returnsNonefor missing keys, sogetattr(section, "key", default)never applies its default._BaseConfig.get(section)takes exactly one argument and raises on unknown sections;conf.get(name, default)is aTypeError, not a fallback.run_tool()returns stdout only.extractor_ctx()wraps extractors inexcept Exception: log.exception(...), so aTypeErrorin an extractor looks like an empty result.Also adds a one-line entry to the Important Commands list and a
changelog.mdentry.