Skip to content

have thirdshift <github issue url> run git pull first in the dir you run it in #74

Closed

Description

@JacobStephens2

Problem Statement

Before starting a Run, I routinely git pull in the Launch directory, usually on the default branch, so that I'm working with the latest code. The Run itself doesn't need this: it already fetches origin and builds its worktree from origin/<Base branch>, so the worktree always starts from the newest code on origin. But my own checkout of the Base branch in the Launch directory falls behind unless I remember to pull by hand, and that manual step is a chore I want thirdshift to do for me on my machine. Not everyone would want thirdshift to touch their Launch directory, so it must be opt-in.

Solution

A new User config setting, launch.pull:

[launch]
pull = true

When it is on, every Run on that machine brings the Launch directory's checkout of the Base branch up to date with origin. It uses a fast-forward-only update, after the pre-flight checks pass and before the worktree is created. It is off by default. If the update can't happen, thirdshift prints a warning: line and carries on, because the Run doesn't depend on it. When the branch checked out in the Launch directory isn't the Base branch, the update is skipped.

User Stories

  1. As a thirdshift user, I want my Launch directory's Base branch brought up to date at the start of a Run, so that I no longer have to git pull by hand before every Run.
  2. As a thirdshift user, I want this controlled by a setting in my User config, so that I set it once per machine rather than on every command.
  3. As a thirdshift user who doesn't want thirdshift touching my Launch directory, I want the setting off by default, so that a Run with no User config, or one that says nothing about launch.pull, leaves my checkout exactly as it was.
  4. As a thirdshift user, I want to turn it off explicitly with pull = false, so that I can document the choice in my config.
  5. As a thirdshift user, I want a typo such as [launch] pul = true, or a value that isn't true or false, to stop the Run before any work with an error naming the User config file and the offending key, so that a mistake can't silently leave the setting off. This is the same behavior as merge.always.
  6. As a thirdshift user, I want the update to be fast-forward only, so that it never creates a merge commit, never rebases, never opens an editor and never leaves my Launch directory mid-merge while nobody is watching.
  7. As a thirdshift user, I want the update to happen only after the pre-flight checks pass (Origin match, issue open, git identity set, Base branch on origin and not ahead of it), so that a Run that was never going to happen doesn't touch my Launch directory.
  8. As a thirdshift user, I want the update to happen before the worktree is created, so that my Launch directory is current from the very start of the Run.
  9. As a thirdshift user, I want a failed update (for example, uncommitted changes in files the update would overwrite) to print a warning: line on stderr and let the Run continue, so that a convenience never costs me a night's Run.
  10. As a thirdshift user, I want a failed update to leave my uncommitted changes exactly as they were, so that nothing of mine is lost.
  11. As a thirdshift user, I want the warning to say what failed and what I can run by hand, so that I can fix my checkout later.
  12. As a thirdshift user, I want a progress line on stderr when the update runs, so that I can see what thirdshift did to my Launch directory.
  13. As a thirdshift user scripting thirdshift, I want stdout to keep carrying only the pull request URL, so that url=$(thirdshift "$issue") keeps working.
  14. As a thirdshift user, I want a failed update never to change the Run's outcome or exit code, so that success and failure mean the same things they always have.
  15. As a thirdshift user running a Continuation whose open pull request targets a base other than my checked-out branch, I want the update skipped, so that thirdshift never moves a branch that the Run isn't based on.
  16. As a thirdshift user whose Launch directory is already up to date, I want the update to be a quiet no-op, so that nothing changes and nothing alarming is printed.
  17. As a thirdshift user, I want the Run's worktree to keep starting from origin/<Base branch> whether or not the setting is on, so that the setting never changes what code the agent works from.
  18. As a thirdshift user reading the README, I want the User config section to document launch.pull, so that I can discover it and understand what it does and doesn't do.

Implementation Decisions

  • Setting: launch.pull, a boolean in a new [launch] section of the User config, off by default. There is no per-Run flag for now. Parsing follows the existing merge.always rules: an unknown key in [launch], a non-boolean value, or a launch that isn't a table is a config error, reported before any work and naming the file and key. update, version and help still never read the User config.
  • Config to Run: the loaded User config's launch.pull value reaches the Run alongside its Goal. The Run module is the only consumer.
  • Placement in the Run: after preflight::check, Issue branch selection, and the Base branch check. That check already fetches origin/<base> and fails if the local Base branch has commits that origin lacks. The update comes before the interrupt check and worktree creation.
  • Condition: the update runs only when the setting is on and the branch checked out in the Launch directory equals the Base branch. Otherwise it is skipped. A detached HEAD already means either a failed Run (no Base branch) or a Continuation whose pull request supplies the base, so it is always skipped.
  • Operation: a fast-forward-only update of the checked-out Base branch to origin/<base>, which has just been fetched. For example, git merge --ff-only origin/<base> in the Launch directory, which is equivalent to git pull --ff-only origin <base>. It is always against origin, never the branch's configured upstream. This matches Origin match, since origin is the only remote thirdshift knows. Because preflight rejects a local Base branch that is ahead of origin, the only outcomes are already up to date, fast-forwarded, or blocked by the working tree.
  • Failure handling: any error from the update becomes a warning: line on stderr carrying git's error. The Run then continues unchanged. This is the same warning: convention used after a Self-merge.
  • Output: a progress line on stderr when the update is attempted. Nothing new goes to stdout.
  • Glossary: Launch directory is now defined in CONTEXT.md, and the User config entry mentions this setting.
  • No ADR: the decision is cheap to reverse and not surprising.

Testing Decisions

  • A good test drives the real binary and asserts on externally visible results: the Launch directory's branch head, its working tree, stderr lines, stdout, and exit code. It does not assert on which git commands ran.
  • End-to-end, through the existing Scenario harness (real bare origin, real Launch directory, fake gh and claude). Prior art: the User config tests (merge_always_*) and the preflight tests. Cases:
    • [launch] pull = true with origin's Base branch ahead of the Launch directory: afterwards the Launch directory's Base branch is at origin's head, and the Run succeeds as usual.
    • No User config, and pull = false: the Launch directory's Base branch is unchanged.
    • An uncommitted change to a file that origin's new commits also change: stderr has a warning: line, the Run still exits 0 with the pull request URL on stdout, and the uncommitted change is intact.
    • A Continuation whose open pull request targets a base other than the checked-out branch: the checked-out branch is not moved.
    • A Run stopped by pre-flight (for example, a closed issue or an Origin match failure): the Launch directory's branch is not moved.
  • Unit, in the config module (prior art: merge_always_is_read, errors_name_the_file_and_the_offending_key): launch.pull is read, a non-boolean value is an error, and an unknown launch.* key is an error.

Out of Scope

  • A per-Run flag (--pull / --no-pull).
  • Updating any branch other than the Base branch, or pulling from any remote other than origin.
  • Plain git pull semantics: merge, rebase, or anything that honors pull.rebase / pull.ff config.
  • Stashing, resetting, or otherwise touching uncommitted changes to make the update succeed.
  • Changing where the worktree starts. It stays origin/<Base branch>.
  • Updating the Launch directory again at the end of the Run, for example after a Self-merge.

Further Notes

  • The setting is purely a convenience for the Launch directory. The Run's worktree already starts from freshly fetched origin/<Base branch>, so the agent's code is current whether or not the setting is on.
  • Glossary update (Launch directory) came out of the grilling session for this issue.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions