fix(submit): only prompt to publish draft when base transitions to trunk by boneskull · Pull Request #100 · boneskull/gh-stack · GitHub
Skip to content

fix(submit): only prompt to publish draft when base transitions to trunk - #100

Merged
boneskull merged 2 commits into
mainfrom
issue-95
Apr 20, 2026
Merged

boneskull merged 2 commits into
mainfrom
issue-95

Conversation

@boneskull

Copy link
Copy Markdown
Owner

Resolves #95. Previously, gh stack submit would prompt to publish a draft PR every time it ran, as long as the PR was a draft and targeted trunk. This got annoying fast.

The fix adds a stackPRBase key to .git/config (per branch) that persists the last-known remote base after each successful submit run. The publish prompt now fires only when the stored base differs from trunk — i.e., the PR's base is genuinely moving to trunk for the first time since the last submit.

Cleanup is consistent: unlink, orphan, and sync's merged-branch handlers all clear stackPRBase alongside the other per-branch config keys. Sync's retarget-to-trunk path also persists the new base so subsequent submit runs don't re-prompt.

@boneskull

Copy link
Copy Markdown
Owner Author

@claude

claude Bot commented Apr 19, 2026 •

Copy link
Copy Markdown

Claude finished @boneskull's task in 4m 42s —— View job


Code Review

  • Gather context and read changed files
  • Review internal/config/config.go and internal/config/config_test.go
  • Review cmd/submit.go and cmd/submit_internal_test.go
  • Review cmd/sync.go, cmd/orphan.go, cmd/unlink.go
  • Review new e2e tests (e2e/orphan_test.go, e2e/unlink_test.go)

Overall this is well-structured: the new stackPRBase key follows the same patterns as stackPR, stackParent, and stackForkPoint, cleanup is consistent across all paths, and the unit tests for isTransitionToTrunk are thorough. One correctness issue in the prActionUpdate path needs attention before merging.


Bug: SetPRBase is called before isTransitionToTrunk in the prActionUpdate path

cmd/submit.go lines 466–479

if err := ghClient.UpdatePRBase(d.prNum, parent); err != nil {
    fmt.Println(s.Error("failed"))
    fmt.Printf(...)
} else {
    fmt.Println(s.Success("ok"))
    _ = cfg.SetPRBase(b.Name, parent)   // ← (1) stores trunk NOW
    ...
}
...
maybeMarkPRReady(...)  // ← (2) isTransitionToTrunk reads stackPRBase
                       //      always sees trunk → always returns false

Because SetPRBase(b.Name, trunk) is called at (1) before maybeMarkPRReady at (2), by the time isTransitionToTrunk runs it reads stackPRBase == trunk and always returns false. The publish-draft prompt can never fire in the prActionUpdate path — not even on the first submit after the parent branch is merged.

The practical consequence: in the normal flow (run sync after a parent PR merges, then submit), sync calls SetPRBase(child, trunk) when it retargets, and submit also calls SetPRBase before the check. Either way, isTransitionToTrunk sees trunk and suppresses the prompt. The user must manually mark the PR ready.

Compare with adoptExistingPRDirect (cmd/submit.go:729), which correctly works around the same problem by using the live existingPR.Base.Ref instead of the stored value:

_ = pCtx.cfg.SetPRBase(branch, base)  // stored trunk
// ...
if existingPR.Draft && base == pCtx.trunk && existingPR.Base.Ref != pCtx.trunk {
    // uses live ref, not stored base — correctly avoids the ordering issue
    promptMarkPRReady(...)
}

Fix: move maybeMarkPRReady inside the else block, before SetPRBase — or capture the transition state before updating:

} else {
    fmt.Println(s.Success("ok"))
    maybeMarkPRReady(ghClient, cfg, d.prNum, b.Name, parent, trunk, s) // check BEFORE persisting
    _ = cfg.SetPRBase(b.Name, parent)
    ...
}

Fix this →


Minor: maybeMarkPRReady calls GetPR even when base != trunk

cmd/submit.go:762–779 — maybeMarkPRReady returns early if base != trunk (line 765), which is good. But when base == trunk, it fetches the full PR object via ghClient.GetPR(prNumber) just to check pr.Draft. For stacks with many PRs this is one extra API call per PR per submit. Low priority, but if the prDecision struct already carries the draft status (it does — d.draft for prActionCreate), it could be threaded through for the update path too.


Minor: orphanMergedBranch in cmd/sync.go clears stackPRBase and stackPR, but RemoveParent is the only one checked with a non-nil return

cmd/sync.go:489-495 (the new orphanMergedBranch function) — all removals are best-effort (_ = cfg.Remove...), which is consistent with the other cleanup paths. No issue.


Tests

  • TestIsTransitionToTrunk covers all the right cases in isolation.
  • TestDeleteMergedBranchClearsPRBase and TestOrphanMergedBranchClearsPRBase correctly verify cleanup.
  • There's no test that exercises maybeMarkPRReady via the prActionUpdate path, which is why the ordering bug isn't caught. A test that sets stackPRBase = "feat-parent" and verifies the prompt fires (or doesn't fire after the fix) when UpdatePRBase succeeds would catch this.
  • The new e2e tests for orphan and unlink are clean and focused.

TL;DR: The SetPRBase / maybeMarkPRReady ordering in the prActionUpdate path is a correctness bug that silently suppresses the publish-draft prompt. Everything else looks solid.

Resolves #95. Previously, `gh stack submit` would prompt to publish a
draft PR every time it ran, as long as the PR was a draft and targeted
trunk. This got annoying fast.

The fix adds a `stackPRBase` key to `.git/config` (per branch) that
persists the last-known remote base after each successful submit run.
The publish prompt now fires only when the stored base differs from
trunk — i.e., the PR's base is genuinely moving to trunk for the first
time since the last submit.

Cleanup is consistent: `unlink`, `orphan`, and sync's merged-branch
handlers all clear `stackPRBase` alongside the other per-branch config
keys. Sync's retarget-to-trunk path also persists the new base so
subsequent submit runs don't re-prompt.
The `prActionUpdate` path in `executePRDecisions` was calling
`SetPRBase` before `maybeMarkPRReady`, which meant
`isTransitionToTrunk` always read the value we had just written
(trunk) and returned false. The publish-draft prompt was therefore
silently suppressed for all existing PRs transitioning to trunk.

Fix: call `maybeMarkPRReady` first, then persist the new base.
While here, move `GenerateAndPostStackComment` inside the success
branch so it doesn't run when `UpdatePRBase` fails.

Add `TestIsTransitionToTrunkOrderingInvariant` to document and
guard the required call order.
@claude

claude Bot commented Apr 19, 2026 •

Copy link
Copy Markdown

@boneskull
boneskull merged commit c47ca16 into main Apr 20, 2026
8 checks passed
@boneskull
boneskull deleted the issue-95 branch April 20, 2026 00:19
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.

submit: prompt to publish should only happen if the base branch changes to trunk

1 participant