authorgravatar for 75276669+Gigioxx@users.noreply.github.comGuillermo Casanova <75276669+Gigioxx@users.noreply.github.com> 2026-09-04 12:44:01-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 12:44:01-04:00
logd536b0580d044967dc644498d8c5a6e96464f767
treed601f869c0caa9f1785656747971f501a2f29244
parentc66f15f39e61fda0a9a100578833ec12e7526859
signature Signed by PGP key B5690EEEBB952194

fix(server): settle inactive threads with open PRs (#9610)


4 files changed, 23 insertions(+), 10 deletions(-)

apps/server/src/orchestration/ThreadSettlementPolicy.test.ts+2-2
......@@ -95,8 +95,8 @@ describe("resolveAutoSettlementAt", () => {
9595 expect(decide(makeThread({ latestUserMessageAt: "2026-08-25T12:00:00.000Z" }))).toBe(false);
9696 });
9797
98 it("keeps open pull requests active", () => {
99 expect(decide(makeThread(), { state: "open", updatedAt: NOW })).toBe(false);
98 it("settles inactive threads with open pull requests", () => {
99 expect(decide(makeThread(), { state: "open", updatedAt: NOW })).toBe(true);
100100 });
101101
102102 it("settles closed requests and honors the merge setting", () => {
apps/server/src/orchestration/ThreadSettlementPolicy.ts-1
......@@ -81,7 +81,6 @@ export function resolveAutoSettlementAt(input: {
8181 if (pullRequestSettles(thread, pullRequest, input.autoSettleOnMerge)) {
8282 return activityAt ?? thread.createdAt;
8383 }
84 if (pullRequest.state === "open") return null;
8584 }
8685 if (input.autoSettleAfterDays === null || activityAt === null) return null;
8786 return Date.parse(activityAt) < Date.parse(input.now) - input.autoSettleAfterDays * DAY_MS
apps/server/src/orchestration/ThreadSettlementReactor.test.ts+17-3
......@@ -408,6 +408,7 @@ describe("ThreadSettlementReactor", () => {
408408 const fixture = yield* makeHarness({
409409 snapshot: makeSnapshot([
410410 makeThread("merged-in-app", {
411 latestUserMessageAt: "2026-08-27T00:00:00.000Z",
411412 linkedPullRequest: {
412413 projectId: PROJECT_ID,
413414 repository: "owner/repository",
......@@ -415,7 +416,10 @@ describe("ThreadSettlementReactor", () => {
415416 url: "https://example.test/owner/repository/pull/42",
416417 },
417418 }),
418 makeThread("slow-periodic-lookup", { branch: "another-feature" }),
419 makeThread("slow-periodic-lookup", {
420 branch: "another-feature",
421 latestUserMessageAt: "2026-08-27T00:00:00.000Z",
422 }),
419423 ]),
420424 branchPullRequest: () =>
421425 Ref.updateAndGet(branchLookupCount, (count) => count + 1).pipe(
......@@ -460,7 +464,12 @@ describe("ThreadSettlementReactor", () => {
460464 const state = yield* Ref.make<"open" | "merged">("open");
461465 const mergedThreadSettled = yield* Deferred.make<void>();
462466 const fixture = yield* makeHarness({
463 snapshot: makeSnapshot([makeThread("branch-thread", { branch: "saved-feature" })]),
467 snapshot: makeSnapshot([
468 makeThread("branch-thread", {
469 branch: "saved-feature",
470 latestUserMessageAt: "2026-08-27T00:00:00.000Z",
471 }),
472 ]),
464473 branchPullRequest: () =>
465474 Ref.get(state).pipe(
466475 Effect.map((pullRequestState) => ({ state: pullRequestState, updatedAt: NOW })),
......@@ -500,6 +509,7 @@ describe("ThreadSettlementReactor", () => {
500509 const fixture = yield* makeHarness({
501510 snapshot: makeSnapshot([
502511 makeThread("merged-in-app", {
512 latestUserMessageAt: "2026-08-27T00:00:00.000Z",
503513 linkedPullRequest: {
504514 projectId: PROJECT_ID,
505515 repository: "owner/repository",
......@@ -508,6 +518,7 @@ describe("ThreadSettlementReactor", () => {
508518 },
509519 }),
510520 makeThread("unrelated-linked", {
521 latestUserMessageAt: "2026-08-27T00:00:00.000Z",
511522 linkedPullRequest: {
512523 projectId: PROJECT_ID,
513524 repository: "owner/repository",
......@@ -683,7 +694,10 @@ describe("ThreadSettlementReactor", () => {
683694 const fixture = yield* makeHarness({
684695 snapshot: makeSnapshot(
685696 [
686 makeThread("missing-own-project", { linkedPullRequest }),
697 makeThread("missing-own-project", {
698 latestUserMessageAt: "2026-08-27T00:00:00.000Z",
699 linkedPullRequest,
700 }),
687701 makeThread("missing-branch-project", { branch: "saved-feature" }),
688702 ],
689703 [makeProject(LINKED_PROJECT_ID, "/workspace/linked")],
docs/user/thread-sidebar.md+4-4
......@@ -13,10 +13,10 @@ request merges if **Auto-settle merged threads** is enabled.
1313Each server stores its own copy of the automatic settlement settings and checks them even when no
1414web, desktop, or mobile client is connected. By default, it settles threads after three days without
1515activity and when their pull request merges. An eligible idle thread also settles when its pull
16request closes. An open pull request blocks inactivity settlement. Active work, pending input, and
17live background work keep the thread active. T3 Code settles from a closed or merged pull request
18only when its timestamp is not older than the user's latest activity. If that timestamp is not
19available, the inactivity rule still applies. A manual un-settle also keeps the thread active.
16request closes. An open pull request does not block inactivity settlement. Active work, pending
17input, and live background work keep the thread active. T3 Code settles from a closed or merged
18pull request only when its timestamp is not older than the user's latest activity. If that timestamp
19is not available, the inactivity rule still applies. A manual un-settle also keeps the thread active.
2020
2121**Settled** lists threads by when their work finished, newest first. A thread you settle yourself
2222sorts by the moment you settled it. A thread that settled on its own sorts by its last message or