Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [Unreleased]

- fix: handle `gh pr merge` in worktree context - detect worktree and use remote-only merge strategy to avoid `fatal: 'main' is already used by worktree` error (#3)

## [1.0.0] - 2026-02-21

Initial release. Extracted from [agentsys](https://github.com/agent-sh/agentsys) monorepo.
17 changes: 17 additions & 0 deletions commands/ship-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ Proceeding to production...
### Merge to Production Branch

```bash
# Worktree check must come first - multi-branch deployment requires branch checkout
if [ -f "$(git rev-parse --show-toplevel 2>/dev/null)/.git" ]; then
MAIN_REPO=$(dirname "$(git rev-parse --git-common-dir 2>/dev/null)")
echo "[ERROR] Multi-branch deployment is not supported from a worktree"
echo "Run from the main repo: cd $MAIN_REPO"
exit 1
fi

echo "Merging $MAIN_BRANCH → $PROD_BRANCH..."

git checkout $PROD_BRANCH
Expand Down Expand Up @@ -283,9 +291,18 @@ fi

```bash
rollback_production() {
# Worktree check must come first
if [ -f "$(git rev-parse --show-toplevel 2>/dev/null)/.git" ]; then
MAIN_REPO=$(dirname "$(git rev-parse --git-common-dir 2>/dev/null)")
echo "[ERROR] Rollback is not supported from a worktree"
echo "Run from the main repo: cd $MAIN_REPO"
exit 1
fi

echo "========================================"
echo "ROLLBACK INITIATED"
echo "========================================"

echo "WARNING: Force pushing to $PROD_BRANCH to revert"

git checkout $PROD_BRANCH
Expand Down
20 changes: 15 additions & 5 deletions commands/ship-error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,22 @@ git push
### Cancel and Cleanup

```bash
# If you need to abandon the PR
gh pr close $PR_NUMBER --delete-branch
# Detect worktree
IS_WORKTREE="false"
if [ -f "$(git rev-parse --show-toplevel 2>/dev/null)/.git" ]; then
IS_WORKTREE="true"
fi

# Clean up local
git checkout $MAIN_BRANCH
git branch -D $CURRENT_BRANCH
if [ "$IS_WORKTREE" = "true" ]; then
# In worktree: close PR and delete remote branch separately
gh pr close $PR_NUMBER
git push origin --delete "$CURRENT_BRANCH" 2>&1 || echo "[WARN] Remote branch deletion failed - may need manual cleanup"
echo "[OK] PR closed (worktree mode - local cleanup deferred to worktree removal)"
else
gh pr close $PR_NUMBER --delete-branch
git checkout $MAIN_BRANCH
git branch -D $CURRENT_BRANCH
fi
```

## Exit Codes
Expand Down
49 changes: 40 additions & 9 deletions commands/ship.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,17 +348,37 @@ fi

echo "[OK] All comments resolved"

# 3. Merge with strategy (default: squash)
# 3. Detect if running from a worktree
IS_WORKTREE="false"
if [ -f "$(git rev-parse --show-toplevel 2>/dev/null)/.git" ]; then
IS_WORKTREE="true"
echo "[INFO] Running from worktree - using remote-only merge strategy"
fi

# 4. Merge with strategy (default: squash)
STRATEGY=${STRATEGY:-squash}
gh pr merge $PR_NUMBER --$STRATEGY --delete-branch
if [ -z "$OWNER" ] || [ -z "$REPO" ]; then
echo "[ERROR] Failed to extract repo owner/name"
exit 1
fi

# Update local
git checkout $MAIN_BRANCH
git pull origin $MAIN_BRANCH
if [ "$IS_WORKTREE" = "true" ]; then
# In worktree: merge without --delete-branch (it tries to checkout main locally)
gh pr merge $PR_NUMBER --$STRATEGY --repo "$OWNER/$REPO"
# Delete remote branch separately
git push origin --delete "$CURRENT_BRANCH" 2>&1 || echo "[WARN] Remote branch deletion failed - may need manual cleanup"
# Get merge SHA from the PR metadata (avoids race condition with fetch)
MERGE_SHA=$(gh pr view $PR_NUMBER --repo "$OWNER/$REPO" --json mergeCommit --jq '.mergeCommit.oid')
else
gh pr merge $PR_NUMBER --$STRATEGY --delete-branch
# Update local
git checkout $MAIN_BRANCH
git pull origin $MAIN_BRANCH
MERGE_SHA=$(git rev-parse HEAD)
fi

# Update repo-map if it exists (non-blocking)
node -e "const { getPluginRoot } = require('@agentsys/lib/cross-platform'); const pluginRoot = getPluginRoot('ship'); if (!pluginRoot) { console.log('Plugin root not found, skipping repo-map'); process.exit(0); } const repoMap = require(\`\${pluginRoot}/lib/repo-map\`); if (repoMap.exists(process.cwd())) { repoMap.update(process.cwd(), {}).then(() => console.log('[OK] Repo-map updated')).catch((e) => console.log('[WARN] Repo-map update failed: ' + e.message)); } else { console.log('Repo-map not found, skipping'); }" || true
MERGE_SHA=$(git rev-parse HEAD)
echo "[OK] Merged PR #$PR_NUMBER at $MERGE_SHA"
```
</phase-6>
Expand Down Expand Up @@ -438,9 +458,20 @@ if (workflowState) {
### Local Branch Cleanup

```bash
git checkout $MAIN_BRANCH
# Feature branch already deleted by --delete-branch
git branch -D $CURRENT_BRANCH 2>/dev/null || true
# Re-detect worktree in case Phase 6 was skipped or run separately
if [ -z "$IS_WORKTREE" ]; then
IS_WORKTREE="false"
if [ -f "$(git rev-parse --show-toplevel 2>/dev/null)/.git" ]; then
IS_WORKTREE="true"
fi
fi

if [ "$IS_WORKTREE" = "true" ]; then
echo "[OK] Local branch cleanup deferred (worktree mode - cleaned when worktree is removed)"
else
git checkout $MAIN_BRANCH
git branch -D $CURRENT_BRANCH 2>/dev/null || true
fi
```

## Phase 12: Completion Report
Expand Down