@@ -1957,7 +1957,7 @@ func TestCloseoutRequiresExplicitResolutionForAmbiguousLandingPRs(t *testing.T)
19571957 "headRefName": "stack/discovery-core",
19581958 "baseRefName": "main",
19591959 "headRefOid": "` + landingHead + `",
1960- "state": "MERGED ",
1960+ "state": "OPEN ",
19611961 "isDraft": false
19621962 }
19631963 },
@@ -1970,6 +1970,95 @@ func TestCloseoutRequiresExplicitResolutionForAmbiguousLandingPRs(t *testing.T)
19701970 }
19711971}
19721972
1973+ func TestCloseoutSurfacesTrackedSourcePRRefreshFailures (t * testing.T ) {
1974+ repo := testutil .SetupGitRepo (t )
1975+ testutil .Run (t , repo , "git" , "switch" , "-c" , "feature/a" )
1976+ testutil .WriteFile (t , filepath .Join (repo , "feature-a.txt" ), "feature a\n " )
1977+ testutil .Run (t , repo , "git" , "add" , "feature-a.txt" )
1978+ testutil .Run (t , repo , "git" , "commit" , "-m" , "add feature a" )
1979+
1980+ testutil .Run (t , repo , "git" , "switch" , "main" )
1981+ testutil .Run (t , repo , "git" , "switch" , "-c" , "stack/discovery-core" )
1982+ testutil .WriteFile (t , filepath .Join (repo , "landing.txt" ), "landing\n " )
1983+ testutil .Run (t , repo , "git" , "add" , "landing.txt" )
1984+ testutil .Run (t , repo , "git" , "commit" , "-m" , "landing" )
1985+ landingHead := strings .TrimSpace (testutil .Run (t , repo , "git" , "rev-parse" , "HEAD" ))
1986+
1987+ runtime := newTestRuntime (repo )
1988+ state := store.RepoState {
1989+ Version : 1 ,
1990+ Repo : "hack-dance/stack" ,
1991+ DefaultRemote : "origin" ,
1992+ Trunk : "main" ,
1993+ Branches : map [string ]store.BranchRecord {
1994+ "feature/a" : {
1995+ ParentBranch : "main" ,
1996+ RemoteName : "origin" ,
1997+ PR : store.PullRequest {
1998+ Number : 1 ,
1999+ HeadRefName : "feature/a" ,
2000+ BaseRefName : "main" ,
2001+ LastSeenHeadOID : strings .TrimSpace (testutil .Run (t , repo , "git" , "rev-parse" , "feature/a" )),
2002+ State : "OPEN" ,
2003+ },
2004+ },
2005+ },
2006+ Landings : map [string ]store.LandingRecord {
2007+ "stack/discovery-core" : {
2008+ BaseBranch : "main" ,
2009+ SourceBranches : []string {"feature/a" },
2010+ CreatedAt : "2026-03-26T18:40:00Z" ,
2011+ },
2012+ },
2013+ Verifications : map [string ][]store.VerificationRecord {
2014+ "stack/discovery-core" : {
2015+ {
2016+ CheckType : "manual" ,
2017+ Identifier : "check" ,
2018+ Passed : true ,
2019+ HeadOID : landingHead ,
2020+ RecordedAt : "2026-03-26T18:41:00Z" ,
2021+ },
2022+ },
2023+ },
2024+ }
2025+ if err := runtime .Store .WriteState (runtime .Context , state ); err != nil {
2026+ t .Fatalf ("write state: %v" , err )
2027+ }
2028+
2029+ ghStub := testutil .SetupGHStub (t , "hack-dance/stack" , "main" )
2030+ t .Setenv ("STACK_TEST_GH_STATE" , ghStub .StatePath )
2031+ t .Setenv ("STACK_TEST_GH_LOG" , ghStub .LogPath )
2032+ t .Setenv ("PATH" , ghStub .Dir + string (os .PathListSeparator )+ os .Getenv ("PATH" ))
2033+
2034+ writeGHState (t , ghStub .StatePath , `{
2035+ "repo": {
2036+ "nameWithOwner": "hack-dance/stack",
2037+ "url": "https://github.com/hack-dance/stack",
2038+ "defaultBranchRef": { "name": "main" }
2039+ },
2040+ "prs": {
2041+ "9": {
2042+ "id": "PR_9",
2043+ "number": 9,
2044+ "url": "https://example.com/hack-dance/stack/pull/9",
2045+ "repo": "hack-dance/stack",
2046+ "headRefName": "stack/discovery-core",
2047+ "baseRefName": "main",
2048+ "headRefOid": "` + landingHead + `",
2049+ "state": "MERGED",
2050+ "isDraft": false
2051+ }
2052+ },
2053+ "next_number": 10
2054+ }` )
2055+
2056+ output := executeCommand (t , runtime , "closeout" , "stack/discovery-core" )
2057+ if ! strings .Contains (output , `source PR for feature/a could not be refreshed: tracked PR #1 for "feature/a" could not be loaded` ) {
2058+ t .Fatalf ("expected source PR refresh failure guidance, got %q" , output )
2059+ }
2060+ }
2061+
19732062func TestSupersedeRecordsMetadataAndCommentsOriginalPRs (t * testing.T ) {
19742063 repo := testutil .SetupGitRepo (t )
19752064 testutil .Run (t , repo , "git" , "switch" , "-c" , "stack/discovery-core" )
@@ -2685,6 +2774,94 @@ func TestQueueAllowsLandingBranchAndPrintsCloseoutGuidance(t *testing.T) {
26852774 }
26862775}
26872776
2777+ func TestQueueLandingBranchPrefersUniqueOpenPRWhenHistoryExists (t * testing.T ) {
2778+ repo , runtime , _ := setupTrackedStackRepo (t )
2779+ testutil .Run (t , repo , "git" , "switch" , "main" )
2780+ testutil .Run (t , repo , "git" , "switch" , "-c" , "stack/discovery-core" )
2781+ testutil .WriteFile (t , filepath .Join (repo , "landing.txt" ), "landing\n " )
2782+ testutil .Run (t , repo , "git" , "add" , "landing.txt" )
2783+ testutil .Run (t , repo , "git" , "commit" , "-m" , "landing" )
2784+ testutil .Run (t , repo , "git" , "push" , "-u" , "origin" , "stack/discovery-core" )
2785+ landingHead := strings .TrimSpace (testutil .Run (t , repo , "git" , "rev-parse" , "HEAD" ))
2786+
2787+ state := store.RepoState {
2788+ Version : 1 ,
2789+ Repo : "hack-dance/stack" ,
2790+ DefaultRemote : "origin" ,
2791+ Trunk : "main" ,
2792+ Branches : map [string ]store.BranchRecord {},
2793+ Landings : map [string ]store.LandingRecord {
2794+ "stack/discovery-core" : {
2795+ BaseBranch : "main" ,
2796+ SourceBranches : []string {},
2797+ CreatedAt : "2026-03-26T18:40:00Z" ,
2798+ },
2799+ },
2800+ Verifications : map [string ][]store.VerificationRecord {
2801+ "stack/discovery-core" : {
2802+ {
2803+ CheckType : "manual" ,
2804+ Identifier : "check" ,
2805+ Passed : true ,
2806+ HeadOID : landingHead ,
2807+ RecordedAt : "2026-03-26T18:41:00Z" ,
2808+ },
2809+ },
2810+ },
2811+ }
2812+ if err := runtime .Store .WriteState (runtime .Context , state ); err != nil {
2813+ t .Fatalf ("write state: %v" , err )
2814+ }
2815+
2816+ ghStub := testutil .SetupGHStub (t , "hack-dance/stack" , "main" )
2817+ t .Setenv ("STACK_TEST_GH_STATE" , ghStub .StatePath )
2818+ t .Setenv ("STACK_TEST_GH_LOG" , ghStub .LogPath )
2819+ t .Setenv ("PATH" , ghStub .Dir + string (os .PathListSeparator )+ os .Getenv ("PATH" ))
2820+
2821+ writeGHState (t , ghStub .StatePath , `{
2822+ "repo": {
2823+ "nameWithOwner": "hack-dance/stack",
2824+ "url": "https://github.com/hack-dance/stack",
2825+ "defaultBranchRef": { "name": "main" }
2826+ },
2827+ "prs": {
2828+ "9": {
2829+ "id": "PR_9",
2830+ "number": 9,
2831+ "url": "https://example.com/hack-dance/stack/pull/9",
2832+ "repo": "hack-dance/stack",
2833+ "headRefName": "stack/discovery-core",
2834+ "baseRefName": "main",
2835+ "headRefOid": "` + landingHead + `",
2836+ "state": "OPEN",
2837+ "isDraft": false
2838+ },
2839+ "10": {
2840+ "id": "PR_10",
2841+ "number": 10,
2842+ "url": "https://example.com/hack-dance/stack/pull/10",
2843+ "repo": "hack-dance/stack",
2844+ "headRefName": "stack/discovery-core",
2845+ "baseRefName": "main",
2846+ "headRefOid": "` + landingHead + `",
2847+ "state": "MERGED",
2848+ "isDraft": false
2849+ }
2850+ },
2851+ "next_number": 11
2852+ }` )
2853+
2854+ output := executeCommand (t , runtime , "queue" , "stack/discovery-core" , "--yes" )
2855+ if ! strings .Contains (output , "PR #9" ) {
2856+ t .Fatalf ("expected queue output to use the unique open landing PR, got %q" , output )
2857+ }
2858+
2859+ log := readFile (t , ghStub .LogPath )
2860+ if ! strings .Contains (log , "pr merge 9 --auto --merge --match-head-commit " + landingHead ) {
2861+ t .Fatalf ("expected queue to merge open landing PR #9, got %q" , log )
2862+ }
2863+ }
2864+
26882865func TestQueueRejectsLandingBranchWithStaleVerification (t * testing.T ) {
26892866 repo := testutil .SetupGitRepo (t )
26902867 remote := filepath .Join (t .TempDir (), "remote.git" )
0 commit comments