Skip to content

Commit 97df523

Browse files
committed
fix(server): complete the settle-threads adoption
Three follow-ups the cherry-pick did not carry. #8600 rewrites isUnpublishedBranch to treat a preserved branch.<name>.remote and .merge pair as evidence the branch was published. That hunk applied to neither a conflict nor a type error — it simply did not land, leaving the old remote-refs-only check. Its own test caught it. With the rewrite in place, Pylon's pruned-branch skip is no longer the right answer: prune removes the tracking ref but leaves the config, so the branch is now distinguishable from one that was never published and the lookup should run rather than fall back to the last-known PR. Pylon's test asserted the saved API call; it now asserts the live one. ws.ts keeps upstream's narrowing from parkingCommand back to archiveCommand. That is not a lost Pylon feature — upstream had parkingCommand too, and #8600 moves settle cleanup into ProviderCommandReactor's thread.settled handler, which dispatches the same onlyIfSettled stop and also covers settlements with no client attached.
1 parent 0135cae commit 97df523

3 files changed

Lines changed: 40 additions & 16 deletions

File tree

apps/server/src/git/GitManager.test.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,19 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
15231523
updatedAt: "2026-04-02T10:00:00Z",
15241524
},
15251525
]),
1526+
// @effect-diagnostics-next-line preferSchemaOverJson:off
1527+
JSON.stringify([
1528+
{
1529+
number: 512,
1530+
title: "Merged then pruned",
1531+
url: "https://github.com/pingdotgg/t3code/pull/512",
1532+
baseRefName: "main",
1533+
headRefName: "feature/merged-then-pruned",
1534+
state: "MERGED",
1535+
mergedAt: "2026-04-02T10:00:00Z",
1536+
updatedAt: "2026-04-02T10:00:00Z",
1537+
},
1538+
]),
15261539
],
15271540
},
15281541
});
@@ -1532,11 +1545,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
15321545
expect(first.pr?.state).toBe("merged");
15331546

15341547
// Merging on the host deletes the remote head, and a prune drops the
1535-
// remote-tracking ref along with `@{upstream}`. From remote refs alone
1536-
// that is indistinguishable from a branch that was never published, so
1537-
// the unpublished-branch skip fires here. Skipping means "we did not
1538-
// ask", not "there is no PR": blanking the badge would also deny
1539-
// auto-settle the merged state it waits for.
1548+
// remote-tracking ref along with `@{upstream}`. #8600 made that
1549+
// distinguishable from a never-published branch: prune leaves
1550+
// `branch.<name>.remote` and `.merge` behind as evidence the branch was
1551+
// published, so the lookup runs instead of skipping and the badge comes
1552+
// from live state rather than the last-known fallback.
15401553
yield* runGit(repoDir, ["push", "origin", "--delete", "feature/merged-then-pruned"]);
15411554
yield* runGit(repoDir, ["fetch", "--prune", "origin"]);
15421555

@@ -1545,8 +1558,8 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
15451558

15461559
expect(second.pr?.number).toBe(512);
15471560
expect(second.pr?.state).toBe("merged");
1548-
// The saved API call is the point of the skip, so it must still be saved.
1549-
expect(ghCalls.filter((call) => call.startsWith("pr list "))).toHaveLength(1);
1561+
// The branch is now identifiable as published, so asking again is correct.
1562+
expect(ghCalls.filter((call) => call.startsWith("pr list "))).toHaveLength(2);
15501563
}),
15511564
);
15521565

apps/server/src/git/GitManager.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,7 @@ export const make = Effect.gen(function* () {
14061406
*/
14071407
const isUnpublishedBranch = Effect.fn("isUnpublishedBranch")(function* (
14081408
cwd: string,
1409-
headContext: Pick<BranchHeadContext, "headBranch">,
1409+
headContext: Pick<BranchHeadContext, "headBranch" | "localBranch">,
14101410
) {
14111411
if (headContext.headBranch.length === 0) {
14121412
return false;
@@ -1421,13 +1421,24 @@ export const make = Effect.gen(function* () {
14211421
})
14221422
.pipe(Effect.map((result) => result.stdout.trim().length > 0));
14231423

1424-
return yield* Effect.all(
1425-
[matchesRef("refs/remotes"), matchesRef(`refs/remotes/*/${headContext.headBranch}`)],
1426-
{ concurrency: "unbounded" },
1427-
).pipe(
1428-
Effect.map(([tracksAnyRemote, tracksThisBranch]) => tracksAnyRemote && !tracksThisBranch),
1429-
Effect.orElseSucceed(() => false),
1430-
);
1424+
return yield* Effect.gen(function* () {
1425+
const [configuredRemote, configuredMerge] = yield* Effect.all(
1426+
[
1427+
gitCore.readConfigValue(cwd, `branch.${headContext.localBranch}.remote`),
1428+
gitCore.readConfigValue(cwd, `branch.${headContext.localBranch}.merge`),
1429+
],
1430+
{ concurrency: "unbounded" },
1431+
);
1432+
if (configuredRemote !== null && configuredMerge !== null) {
1433+
return false;
1434+
}
1435+
1436+
const [tracksAnyRemote, tracksThisBranch] = yield* Effect.all(
1437+
[matchesRef("refs/remotes"), matchesRef(`refs/remotes/*/${headContext.headBranch}`)],
1438+
{ concurrency: "unbounded" },
1439+
);
1440+
return tracksAnyRemote && !tracksThisBranch;
1441+
}).pipe(Effect.orElseSucceed(() => false));
14311442
});
14321443

14331444
const findOpenPr = Effect.fn("findOpenPr")(function* (

docs/user/thread-sidebar.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Each environment owns its automatic settlement settings. The server checks them
1515
desktop, or mobile client is connected. By default, it settles threads after three days without
1616
activity and when their pull request merges. An eligible idle thread also settles when its pull
1717
request closes. An open pull request blocks inactivity settlement. Active work, pending input, and
18-
live background work keep the thread active. T3 Code settles from a closed or merged pull request
18+
live background work keep the thread active. Pylon settles from a closed or merged pull request
1919
only when its timestamp is not older than the user's latest activity. If that timestamp is not
2020
available, the inactivity rule still applies. A manual un-settle also keeps the thread active.
2121
Change these rules in **Settings > General** for the environment. A settings change affects future

0 commit comments

Comments
 (0)