Problem
A sidebar pin (pinnedAt) is an explicit keep-active signal, but the server currently auto-settles pinned threads anyway:
isAutoSettlementCandidate in apps/server/src/orchestration/ThreadSettlementPolicy.ts never checks pinnedAt, so pinned threads are swept for both inactivity and merged/closed-PR settlement.
decider.ts thread.auto-settle has no pinned guard — it even emits a companion thread.unpinned event, silently dropping the user's pin.
- Docs (
docs/user/thread-sidebar.md) state: "Pinning does not prevent automatic settlement."
The existing ThreadSettlementPolicy.test.ts case is even named "blocks pins, ..." but only covers settledOverride: 'active' (the transient keep-active override), not the sidebar pin.
Related:
Proposal
isAutoSettlementCandidate returns false when pinnedAt != null (covers inactivity + PR paths, skips PR lookups).
thread.auto-settle decider rejects when pinnedAt != null (stale-sweep race: pin landed after the snapshot), while thread.settle (manual) still succeeds and clears the pin.
- Docs updated: pinning prevents automatic settlement.
Acceptance
Problem
A sidebar pin (
pinnedAt) is an explicit keep-active signal, but the server currently auto-settles pinned threads anyway:isAutoSettlementCandidateinapps/server/src/orchestration/ThreadSettlementPolicy.tsnever checkspinnedAt, so pinned threads are swept for both inactivity and merged/closed-PR settlement.decider.tsthread.auto-settlehas no pinned guard — it even emits a companionthread.unpinnedevent, silently dropping the user's pin.docs/user/thread-sidebar.md) state: "Pinning does not prevent automatic settlement."The existing
ThreadSettlementPolicy.test.tscase is even named "blocks pins, ..." but only coverssettledOverride: 'active'(the transient keep-active override), not the sidebar pin.Related:
settledOverridevs sidebarpinnedAt)Proposal
isAutoSettlementCandidatereturns false whenpinnedAt != null(covers inactivity + PR paths, skips PR lookups).thread.auto-settledecider rejects whenpinnedAt != null(stale-sweep race: pin landed after the snapshot), whilethread.settle(manual) still succeeds and clears the pin.Acceptance