Repository navigation
[Bug]: Closed-PR threads auto-settle again after every completed turn #6417
Description
Activity
the forced auto-settle feature is frustrating. It is compounded by the re-settle on every turn.
My workaround: pin every thread I have so that it stays in the active thread list even if it is settled, but then that moves it in the list to a place I don't necessarily want it. Trade-offs everywhere.
Reacted by Adamulek123, Steve, Isaac Sherrill and nathanjansenandrewfree commented
on Aug 14, 2026 More actionsI have a live confirmation of this exact state transition from a real Codex-backed thread.
Environment:
- T3
v0.0.34-nightly.20260814.1090 - macOS desktop connected to an Ubuntu remote environment
- the thread's branch resolves to a PR that is
closedand not merged - the user-visible task was still unfinished; a separately supervised QEMU job and its safety guard remained active
Read-only inspection of T3's projection/event database showed:
- The long turn completed at
2026-08-14T11:59:31Z, after which the UI placed the thread in Settled. - There was no
thread.settledevent and nothread.archivedevent. The thread hadarchived_at = NULL,settled_override = NULL, andsettled_at = NULL. This was derived auto-settlement, not an archive or explicit settle. - The user explicitly un-settled it at
12:11:37Z(thread.unsettled, reasonuser). - Sending the next message at
12:11:49Zproducedthread.unsettled, reasonactivity, which cleared the keep-active override back to neutral. - That turn completed at
12:12:38Z; the UI immediately classified the thread as Settled again. - The same sequence began a second time: explicit un-settle at
12:14:33Z, then new activity at12:14:42Z.
That matches the transition table in this issue exactly: explicit Un-settle temporarily sets
"active", real activity clears it tonull, the running session blocks settlement only while the turn is active, and completion exposes the still-closedPR rule again.One correction to the initial user-facing diagnosis: Codex
get_goalreturnednull, but that was not causal. T3'seffectiveSettledpath does not consume Codex goal state. The final response correlated with the move only because turn completion removed the running-session blocker. Also, the thread was never actually archived; “Settled” simply felt like archival because it left the active list.This is therefore a concrete production reproduction of #6417, not a missing-goal issue. A second, separate concern is that user-visible background work can remain live after the parent turn completes, but the closed-PR rule alone is sufficient to explain this reproduction.
- T3
Fix is in #7178. Closed-but-unmerged PRs no longer auto-settle after every turn; merged PRs still do.
Reacted by andrewfreeReacted by Marston ConnellI have auto-settle turned off and they still auto-settle after every turn. Every new thread I create attaches itself to an already merged PR, so every new thread is auto settle after the first turn even with this setting off
I have auto-settle turned off and they still auto-settle after every turn. Every new thread I create attaches itself to an already merged PR, so every new thread is auto settle after the first turn even with this setting off
Are you on the latest nightly? You didn't include the build version you are having issues with or repeatable conditions.
The live confirmation andrewfree posted earlier shows the whole wipe sequence: explicit un-settle writes the override, the automatic activity un-settle clears it. I traced where that happens in the code and have a fix.
The reducer handles every unsettle event with:
settledOverride: payload.reason === "user" ? "active" : null
so any
reason: "activity"event wipes a user's pin back tonull, and the merged/closed-PR check settles the thread again when the turn completes. The part that makes it unavoidable: the decider only emits the activity un-settle whensettledOverride !== null, so clicking Un-settle creates the exact state that triggers the event that erases it. An explicit Un-settle can never survive past one turn.The fix I went with: emit the automatic activity un-settle only when the override is
"settled", so a user's"active"pin stays until the user settles the thread themselves. That is the same small guard at the three emit sites inapps/server/src/orchestration/decider.ts, and theapps/servertest suite passes with the change. PR coming right after this comment.Repro on 0.0.33 stable, macOS 26.5, desktop app: take a thread whose most recent branch PR is merged with no newer PR open, click Un-settle, send any message. The badge re-settles when the turn completes. In
orchestration_eventsI can watch the user pin written at 15:21:10Z and wiped by the activity event at 15:21:16Z.Fix is up as #8015.
Reacted by Adamulek123Fix is in #7178. Closed-but-unmerged PRs no longer auto-settle after every turn; merged PRs still do.
But why? At the very least, this should be a configurable behavior.
There are those who work in non-standard git flows, e.g. maintaining one long-running branch (e.g. "dev") which sees many PRs from it. Having one PR merged from the current branch history does not necessarily mean each subsequent turn should continue auto-settling the thread. This is just wrong, and very annoying.
Reacted by Matt Holla, Marston Connell and Adamulek123It should be a configurable setting imo. Sometimes I leave the main checkout on a PR that closed because I forget and it settles every message I get back
Reopening: #8600 fixes the routine re-settlement, but still uses the PR's
updatedAt. A comment or metadata edit after resumed work can settle a closed-PR thread again, even with inactivity auto-settle disabled. That gap is explicitly covered by this report. My initial closure treated this partial fix as complete.
Before submitting
Area
packages/client-runtime (affects apps/web, apps/desktop, and apps/mobile)
Steps to reproduce
This is deterministic whenever the branch's resolved PR state remains
closed.Expected behavior
Continuing work after a PR closes is real new activity. After the agent finishes, the thread should remain in the active list with its normal Done state. It should not jump into Settled merely because the branch still has a historical closed PR.
A merged PR may reasonably remain an unconditional terminal signal. A closed-but-unmerged PR is different: users commonly continue in the same thread to revise the approach, push more commits, or prepare to reopen/replace the PR.
Actual behavior
The thread is temporarily active while the agent runs, then classifies as settled again as soon as the running blocker disappears. Depending on render/subscription timing, this can look like the row bouncing between Settled and Working/Done.
Manually un-settling does not provide a durable workaround: sending the next message intentionally clears the
"active"override, so the same closed PR wins again after every completed turn.Disabling Auto-settle inactive threads does not help. The closed-PR short-circuit runs before the inactivity setting.
Impact
Major degradation or frequent failure
This affects every follow-up turn on a thread whose resolved PR is closed. It makes the active list unreliable and requires repeatedly finding and un-settling the same thread.
Version or commit
main @
1e59b4c4004ce3c724d09ca0b140ed4523758d1e(inspected 2026-08-13)Environment
Observed in T3 Code on Windows. The classification is shared client-runtime logic, so the same behavior applies to:
The bug is provider-independent once VCS status reports the PR as
closed.State-transition data
The relevant state sequence is:
settledOverrideactiveclosednullclosednullclosednullclosedThat result follows directly from two independent rules:
apps/server/src/orchestration/decider.tsclears any settle override when a new user message is sent, including the explicit"active"keep-active override.packages/client-runtime/src/state/threadSettled.tsreturnstrueunconditionally forchangeRequestState === "merged" || changeRequestState === "closed"after activity blockers disappear.The existing unit tests explicitly assert that closed PRs behave like merged PRs and settle immediately even when inactivity auto-settle is disabled.
Additional implementation findings
open | closed | merged, but no reliableclosedAt.updatedAtis not a reliable substitute forclosedAt, since comments or metadata edits can update a PR after closure.Suggested fix
The smallest predictable behavior change is:
In practice, remove
closedfrom the unconditional terminal branch ineffectiveSettled. Keep historical closed PR association for badges and navigation.A more nuanced rule—auto-settle at closure but stay active after post-close work—would require transporting a reliable closure timestamp through the VCS status contract for every source-control provider. That is a larger contracts/server/web/mobile change and should not use
updatedAtas a proxy.Acceptance criteria
Relevant code
packages/client-runtime/src/state/threadSettled.ts— sharedeffectiveSettledpredicatepackages/client-runtime/src/state/threadSettled.test.ts— current merged/closed truth tableapps/server/src/orchestration/decider.ts— activity clears the settle overrideapps/server/src/git/GitManager.ts— latest PR lookup includes terminal PRspackages/contracts/src/git.ts— VCS status exposes state but no closure timestampapps/web/src/components/Sidebar.tsx— web/desktop PR-state reporting and partitionapps/web/src/components/ChatView.tsx— open-thread settled banner classificationapps/mobile/src/features/threads/threadListV2.ts— shared mobile partitionapps/mobile/src/features/home/HomeScreen.tsxandThreadNavigationSidebar.tsx— mobile entry pointsapps/web/src/components/settings/SettingsPanels.tsx— current “merged or closed PRs always settle” copyRelated issues
"active"override. That feature would be a workaround; it does not correct the default closed-PR lifecycle.Workaround
Manually un-settle the thread again after every completed turn, or move the follow-up work to a new branch/thread without a closed PR association. Disabling inactivity auto-settle alone does not work.