git-coda — One-Shot Ship Workflow
git-coda is the project name; coda (finale) is the closing section of a movement, gathering the entire development history into a single note and landing back on main. The verb for the workflow is still ship.
Goal & Success Criteria
Take the current workspace changes and run them through this complete path:
Current changes → Create new branch from latest main → commit → PR → squash merge → back to main
Calling ship signifies authorization to execute the entire workflow. The process should run continuously without asking for confirmation on branch names, commit messages, PR titles, PR descriptions, conflict resolutions, verification fixes, or whether to continue.
This authorization includes:
- Modifying current functional code to make it compatible with the live functionality of the latest
main. - Fixing test, lint, type, build, and CI issues caused by the current changes or conflicts.
- Creating necessary fix commits and pushing them repeatedly.
- Syncing with the latest
mainbefore merging, resolving new conflicts, and re-verifying.
The workflow is only considered complete when all of the following conditions are met:
- The PR has been successfully merged into
main; "PR created" or "auto-merge enabled" is not sufficient. - Required checks have passed, or the repository has no runnable checks.
- The local branch has returned to
mainand has been fast-forwarded to the merged commit if possible.
Core Execution Principles
- The default result of
shipis "successful release", not "found issues and handed them back to the user". - When a command fails, diagnose the cause first and resume the workflow; do not mechanically repeat the same failed command.
- Use the current live functionality on
origin/mainand the product goal of the current changes as joint constraints; prioritize retaining both. - Fix the root cause; do not fake passing by skipping tests, deleting assertions, disabling rules, or using
--no-verify. - Do not use destructive methods like force push,
reset --hard, discarding commits, or overwriting user changes. - Only pause when there is a lack of authentication/permissions, a need for external approval, persistent platform/network unavailability, or when the intent truly cannot be determined from the code and context. When pausing, explain only the single action required from the user.
Step 0: Information Gathering
Before starting, check the current state:
git status --short --branch
git branch --show-current
git diff --stat
git diff --cached --stat
git remote -v
gh auth status
Record the current branch, uncommitted changes, and unpushed commits to avoid losing user work. If there are no uncommitted changes, check if there are commits on the current branch pending release relative to origin/main. If neither exists, indicate there is nothing to publish and stop.
Automatically determine the following information:
1. Branch Name
- Use directly if already provided by the user.
- Otherwise, infer from the changes in the format
<type>/<short-desc>. - Follow existing branch prefixes or naming conventions in the target repository; use a semantic name if there is no convention.
- If a branch with the same name already exists locally or remotely, automatically append a short suffix to avoid interruption due to naming conflicts.
2. Commit Message
- Use directly if already provided by the user.
- Otherwise, generate a Conventional Commits format from the full diff:
<type>(<scope>): <description>.
3. PR Title and Body
- PR title defaults to the commit message.
- PR body is automatically generated from the diff, containing 2–3 bullet points, an overview of changed files, and verification results.
- Do not separately ask for the PR name or body.
After determining these, briefly inform the user of the branch and commit to be used, then continue immediately without waiting for a reply.
Step 1: Sync Main Branch
If there are workspace changes, first create a stash with a unique identifier and record the corresponding reference; do not create an empty stash if there are no changes.
git stash push --include-untracked -m "git-coda: <timestamp>"
git fetch --prune origin
After fetching, check for commits pending release based on the original branch and HEAD recorded in Step 0. If the original feature branch already has commits, keep that branch, merge origin/main directly, then restore the current stash; do not switch to main first and then decide whether the original branch has commits. Only create a new release branch from origin/main if the original branch has no commits pending release.
First, verify the target main branch from the remote HEAD or repository configuration; main below is a placeholder; if the repository uses master or another name, replace all occurrences with the actual branch.
Inform the user of the latest main branch commit hash.
If the local main cannot be fast-forwarded:
- Check for local unique commits; do not delete or overwrite them.
- Create the release branch directly from the latest
origin/mainand continue the release process. - When finally returning to
main, keep these local commits and clearly state that the localmainwas not overwritten; this does not affect the completed online merge.
Step 2: Create Branch
git checkout -b <branch-name> origin/main
If the current work is already on a feature branch with pending release commits, you may continue using it; first merge the latest origin/main and resolve any conflicts by following the "Conflict Handling Process". Do not re-commit the same changes.
Step 3: Restore Changes and Commit
git stash apply <recorded-stash>
git add -A
git commit -m "<commit-message>"
If stash apply results in conflicts, resolve them following the "Conflict Handling Process". After confirming all changes have been restored and committed, delete the stash created during this process; do not accidentally delete any pre-existing user stashes.
If commit hooks fail, treat them as verification failures: fix the root cause, re-stage, and re-commit without using --no-verify.
Conflict Handling Process
The following process applies to stash apply, merging the latest main, non-fast-forward remote branches, and PR merge conflicts.
-
Gather facts:
git status git diff --name-only --diff-filter=U git log --oneline --left-right origin/main...HEAD -
Examine the base, current branch, and other branch versions of each conflicting file, and read the relevant callers, tests, types, configurations, and recent commits. Do not just look at the conflict markers, and do not blindly select
oursortheirsfor the entire file. -
Determine the intent on both sides:
origin/mainrepresents the current live behavior; it must be preserved unless the current feature explicitly replaces it.- The current branch represents the goal of the current feature; do not silently delete its changes to resolve a conflict.
- If both sides add valid functionality, integrate the interfaces, data flows, and boundary conditions so both work.
- Update conflicting tests according to the final product behavior; regenerate lock files and generated files using the repository's designated tool.
-
Edit until there are no conflict markers, execute
git add, and confirmgit diff --name-only --diff-filter=Uis empty. -
Run minimal verification related to the conflicting files, then run the full verification required by the repository.
-
Create a merge or fix commit and continue the original workflow.
If it is impossible to determine the correct resolution solely from the conflict blocks, continue gathering evidence from the implementation, tests, call chains, commit history, and project documentation. Only ask the user if the two behaviors are mutually exclusive and the product intent remains unclear, and present the options, their impact, and a clear recommendation in one go.
Step 3.5: Local Verification
Based on the current changes and repository documentation, look for existing verification commands:
- First read
AGENTS.md,CONTRIBUTING.md, andREADME.md. - Then check project files like
package.json,pyproject.toml,Makefile, and CI configuration. - If necessary, use the project's existing package manager (based on lock files) to install dependencies; do not upgrade dependencies without reason.
- First run checks directly related to the scope of the changes, then run lint, typecheck, test, or build as required by the repository.
- If no trusted commands are found, clearly inform the user which verifications were skipped and why; do not invent commands.
Verification Failure Fix Loop
Do not pause when verification fails; handle it with the following loop:
- Keep the complete error output, identify the first root cause, not just the last cascading error.
- Determine whether the failure is from product code, test expectations, types, formatting, dependencies, configuration, or the environment.
- Apply the minimal correct fix that satisfies both the live functionality and the current feature:
- Prefer the repository's existing formatter or lint autofix, then handle remaining issues.
- Fix code defects by modifying code.
- Only update tests if the current feature intentionally changes behavior and the original expectation is now outdated; do not weaken, skip, or delete valid tests.
- If the environment is missing dependencies, restore it according to the project lock files; do not use this as an opportunity to perform unrelated upgrades.
- First re-run the failed minimal check, then re-run all required checks.
- If new failures appear, continue diagnosing and fixing until all pass.
Fixes made before the first push can be included in the current commit; fixes made after the first push should be created as new commits and pushed normally; do not force push to clean up history. As long as each round produces new evidence or substantial progress, continue advancing; do not give up due to the number of failures.
Step 4: Push and Create PR
git push -u origin <branch-name>
When push is rejected, handle based on the type:
- Remote branch with the same name contains unrelated content: automatically use a unique branch name and push.
- Remote branch with the same name belongs to the same feature and has new commits: fetch and merge the remote branch, resolve conflicts, verify, and push again.
- Temporary network or GitHub failure: retry briefly and check service recovery.
- Missing authentication or write permission: preserve the full working state and ask the user to complete authorization.
Create a PR using gh pr create:
- Base:
main - Head: new branch
- Title: the commit message
- Body: contains 2–3 bullet points, an overview of changed files, verification results, and
🤖 Finale via git-coda
If a PR already exists, reuse it; do not create a duplicate. After successful creation, show the PR URL to the user, then continue without waiting for a reply.
Step 5: Wait for CI and Fix
Use available repository commands like gh pr checks <pr-number> --watch to wait for required checks.
When checks fail:
- Use commands like
gh pr checksorgh run view --log-failedto read the failing tasks and logs. - If reproducible locally, first reproduce the issue, then fix the root cause following the "Verification Failure Fix Loop".
- Commit the fix, push normally, and wait for CI again.
- Repeat until all required checks pass.
If a check is pending for a long time, inspect the workflow, concurrency queues, and GitHub status; if it's possible to re-run the failed tasks, do so. Only ask the user if a task requires approval from someone with permissions or if an external service is persistently unavailable.
Step 6: Sync with Latest main and Squash Merge
Before merging, sync with the main branch again:
git fetch origin main
git merge --no-edit origin/main
If there are conflicts, resolve them using the "Conflict Handling Process", re-run local verification, push, and wait for CI. Then merge directly:
gh pr merge <pr-number> --squash --delete-branch
If the merge fails due to base update or conflicts, repeat the sync → resolve → verify → push → wait for CI loop, then attempt to merge again. If the repository supports a merge queue, enter the queue and wait until the PR is confirmed to be merged.
Do not bypass branch protection rules. If required approvals, signatures, or permissions are missing, this is an external blocker: display the exact return message from GitHub, explain the steps already completed, and state the single action the user needs to perform.
Step 7: Return to main and Confirm Results
git checkout main
git pull --ff-only origin main
git status --short --branch
gh pr view <pr-number> --json state,mergedAt,mergeCommit,url
Confirm the PR state is "merged", and print the final main commit hash. If the local main has pre-existing unique commits that prevent fast-forwarding, keep them without performing destructive operations; still confirm that the remote PR has been merged and clearly explain the local state.
Print the final status:
🚀 Ship Complete!
✓ Branch <branch-name> merged into main
✓ Local verification and required CI passed
✓ Currently on main, synced with latest code (<commit-hash>)
✓ PR: <pr-url>
Error Handling Principles
- Briefly state what is being done before each step.
- After any command fails, diagnose, fix, and retry, always aiming for a successful merge.
- Do not use force push,
reset --hard, or any other destructive recovery methods. - Proactively resolve conflicts while protecting both the latest live functionality and the current feature's goals.
- Proactively fix test, lint, type check, build, commit hook, and CI failures.
- Do not ask for confirmation on automatically inferred branch names, commit messages, PR titles, PR descriptions, or whether to continue.
- Do not treat repairable failures, multiple debug rounds, or process duration as blockers.
- Only pause for external conditions that the current environment truly cannot resolve; preserve the branch, commits, and logs needed to resume work.
- When pausing, provide only the specific, minimal, actionable user action; the process can be resumed from the same point after completion.
Common Edge Cases
| Case | Handling Method |
|---|---|
| Currently on a non-main branch with only workspace changes | stash → create a new branch from origin/main → apply → resolve conflicts |
| Current branch already has pending release commits | Keep the commits, merge latest origin/main, and continue |
| No uncommitted changes | Check for pending release commits relative to origin/main; only stop if neither exists |
| Branch name already exists | Automatically generate a unique suffix; if a remote branch with the same name is for the same feature, reuse and integrate |
stash apply or merge conflict | Analyze intent on both sides, integrate implementations, verify, and continue |
| lint/test/typecheck/build failures | Fix root cause and loop verification; do not hand failures to the user |
| CI failures | Read logs, reproduce locally, fix, push, wait for next CI round |
| main updated before merge | Merge latest origin/main, resolve conflicts, and re-run the verification loop |
gh not logged in | Prompt to run gh auth login |
| Required manual approval or no write permission | Preserve the state, explain the single external action needed from the user |
User specifies --no-squash | Use --merge |