Repository navigation
feat: make settled threads a lifecycle state - #651
Merged
Merged
Conversation
Applies the review of #651: idle terminals are told by an activity mark and a fresh process inspection instead of shell integration; pending requests are resident-only; manual settle is synchronous; detach-to-idle serves settle and archive only; admission and run dispatch records, the message admission id, activity clocks on the wire, pin state and the PR rule with its grace are removed; one owner validates the 1-90 day range; events re-evaluate only their thread; cold threads fold from the tail of their logs.
The hover Settle / Un-settle control is a ghost xsmall Button; the phone Undo is a ghost button without a shortcut hint; phone Thread behavior lists every project as a row that pushes its rules page, which lists its choices instead of opening a dropdown. Settled rows show and sort by settled_at, then updated_at, and the Failed chip reads SessionActivity.failed.
…ules page A clicked hover button stays hidden once its row moves (focus-visible, not focus, reveals it); the project rules dialog uses the settings dropdown trigger and menu rows; the phone rules page is titled with the project name, which fits the nav bar where the dialog's sentence does not.
Tryanks
marked this pull request as ready for review
October 8, 2026 13:43
This was referenced Oct 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #526. Phase B1 of #535; removes idle auto-archive (#515).
Settled is a lifecycle override with a durable per-thread auto-settle switch, global and per-project inactivity settings (1–90 days or never, validated once in
SettingsPatch::apply), and a Failed chip fromSessionActivity.failed. Explicit archive and orchestratearchive_on_completekeep their behavior. Wire notes are recorded without a version bump.Deferred to #532's follow-up PR: the PR-terminal rule and the two-minute adoption grace (upstream
ThreadSettlementService.ts:86–104), which only matters for PR-driven settlement because inactivity needs at least a day.auto_settle_on_mergeis stored, defaults on, and its row stays hidden. Pins (and settle clearing a pin) are #528's; Undo of settle is un-settle only.Known staged gap: until #532 links PRs, an externally open but unlinked PR can inactivity-settle; later discovery does not reopen it.
Idle terminals follow upstream
TerminalManager.closeIdle(apps/server/src/terminal/Manager.ts:3131–3172): shell-agnostic, a busy shell builtin is not kept. Inspection is unix-only; Windows and an inspection error keep the terminal open. On macOS a$SHELLof/bin/shis a shim that execs bash, so its process name never matches and its terminals stay open.Evidence
After shots are English, from a fresh synthetic profile (one project, 8 active and 14 settled threads), rendered by the real app window through GPUI's Metal
render_to_imagewith scripted input, because screen capture was not permitted in this session; macOS window chrome (traffic lights) is therefore absent. Also inspected, not attached: the expanded shelf with Show 6 more and after it; a viewed settled row under the collapsed shelf; hover Settle and hover Un-settle; the thread menu's Auto-settle caption; the Undo toast on desktop (outline + ⌘Z, and mod+z undoes) and phone (ghost, no hint); the project rules dialog and its menu; the phone rules page (no dropdown or dialog); dark theme at 720 pt. In the desktop-hosted phone preview the 80 pt traffic-light inset truncates "Thread behavior" (as it truncates "Archived Threads" on main); on a device the title box is 185 pt and the phone rules page is titled with the project name.Runtime command-boundary tests: legacy settled data and project overrides survive restart, and a cold thread ages from its log tail, not a rename; manual settle is thread-local, refuses queued human work, approvals and blocking input, cancels automatic queue and async questions, and a later send gets the new provider; message origins survive queue acknowledgement and callback steering, and an uncorrelated provider message stores none; a valid child completion reopens only its parent while a stale, archived or deleted one does not; an unsettled dispatched child holds its lead, settling it detaches only its own provider and releases the lead; real macOS PTYs: an idle shell closes with output kept, a running child keeps its shell, input typed after the settle keeps it (passed with
$SHELLzsh and bash).Checks on the final commit:
Mobile, Web, Linux and Windows run in CI. No credentialed live-provider run.
Large profile, a copy of a real data directory under a throwaway
TCODE_DATA_DIR(debug build, temporary probe outside the diff, copy deleted):The earlier full-log fold took 66 s on the same profile. No equivalence to the old archived-index size is claimed: archived rows left the index, settled rows stay.
Test audit: every removed, added or rewritten case and its CONTRIBUTING decision
Steps refer to CONTRIBUTING's "When a test fails, or is met on the way."
Removed:
auto_archive_respects_idle_rank_exemptions_and_descendant_cascades,group_sessions_places_children_after_their_parent(core/project.rs);unarchived_thread_survives_the_next_auto_archive_sweep(runtime);launch_runs_the_auto_archive_sweep_across_every_project,project_header_resets_only_its_own_thread_expansion(sidebar): Step 2, idle auto-archive, family ordering and active truncation no longer exist; explicit unarchive and per-project collapse remain covered by their existing cases.settled_commands_persist_without_closing_the_selected_conversation,settling_rejects_busy_descendants_and_accepted_input_reactivates_ancestors(runtime): Step 2, the descendant cascade and ancestor reactivation are removed by the issue; the target-only contract is owned bymanual_settlement_is_thread_local_cancels_automatic_queue_and_blocks_human_admissionandchild_completion_admission_reopens_only_its_parent_and_rejects_stale_targets.thread_navigation_matches_displayed_rows_and_disclosures(sidebar): Step 2, replaced bysettled_shelf_pages_without_expanding_for_selection_and_navigation_matches_rows.Renamed or moved:
group_sessions_orders_by_activity→group_sessions_orders_projects_by_activity_or_name(Step 2: the within-project activity order is gone; project ordering kept).orchestrate_send_reactivates_the_child_and_its_settled_parent→orchestrate_send_revives_only_its_archived_child(Step 3: target-only admission; the parent stays settled).compact_families_keep_activity_order_indent_and_collapse→compact_children_keep_indent_and_shared_collapse(Step 1: created-time fixtures drive the specified active order; itsSessionActivityfixture gainsfailed: false).flat_rows_start_reordering_from_their_previous_positions,working_thread_title_stays_inside_row_at_every_sidebar_width(sidebar): moved within the tests module; created-time fixtures, assertions unchanged.Added:
inactivity_settles_at_the_last_activity_unless_blocked(core policy, including provider background work and a pending scheduled wake); runtimemessage_origins_survive_queue_acknowledgement_and_callback_steering,cold_lifecycle_migrates_legacy_settlement_and_ages_from_events_after_rename,manual_settlement_is_thread_local_cancels_automatic_queue_and_blocks_human_admission,settling_closes_only_idle_real_shells_and_spares_new_terminal_input(macOS),child_completion_admission_reopens_only_its_parent_and_rejects_stale_targets(driven throughTurnCompleted, waits withrun_until),settling_detaches_only_its_own_provider_and_releases_its_parent; sidebarsettled_shelf_pages_without_expanding_for_selection_and_navigation_matches_rows.Rewritten, kept:
older_settings_preserve_access_policy_and_accept_partial_feature_blocks(core/settings.rs): Step 2, the deleted archive defaults are replaced by the settlement defaults an older file now gets (3 days, merge on).version_six_index_visits_output_and_elision_literal_json(protocol): Step 2, the literal wire gains"failed":falsein bothSessionActivityliterals and thefailedfixture field;event_envelopes_keep_stored_record_shape_and_optional_request_id: the record fixture gainsorigin: None.reset_settings_clears_preferences_but_keeps_credentials_installs_and_unknown_keys(runtime): Step 2, the archive preference fixture line and its assertion are removed; reset and security assertions kept.turn_running_for_is_independent_of_active_or_parked_location(runtime): Step 3, background work reports Waiting, not Working.space_index_and_scope_project_only_the_members_projects(runtime): Step 1, the fixture host has auto-settle off so startup settlement of its historical threads cannot interleave index events; assertions unchanged.thread_shortcuts_follow_list_order_and_leave_model_picker_numbers_alone(ui/shell.rs): Steps 1 and 3, mounts the gpui-base Root asrun_shelldoes and extends with hover Settle, a focused input keeping mod+z, and mod+z undoing.compact_thread_distinguishes_host_waiting_activity,compact_menu_dismissal_preserves_selection_and_clears_row_surface,compact_scroll_reuses_model_and_renders_only_viewport,grouped_layout_lays_out_only_the_visible_threads,returning_from_a_thread_reveals_its_row_below_the_top_edge,thread_rows_carry_the_provider_mark_until_the_setting_is_off,compact_recent_orders_projects_and_switches_to_persisted_grouped_view(sidebar): Step 1, fixtures follow the specified active order or menu height; observable assertions kept.editable_settings_wait_for_the_host_and_then_keep_the_user_edit(settings_page),model_picker_focuses_its_search_only_off_phone(shell): Step 2, deleted auto-archive fixture fields removed.origin: NoneonStoredEvent/SessionEventRecordliterals (no assertion change): core/session.rsa_turn_finalized_without_a_timestamp_rejects_later_tool_transitions,streamed_deltas_continue_the_open_turn_not_an_earlier_turn_with_the_same_id,synthetic_entry_ids_do_not_depend_on_how_much_earlier_history_is_folded, theathelpers in session.rs and superseded.rs; runtimesession_history_snapshot_pages_and_absolute_tail_cursors,history_snapshot_and_pages_start_at_turn_boundaries,history_byte_budget_preserves_contiguous_records_and_reports_shrinking,history_sends_output_previews_and_reads_whole_outputs_on_request,superseded_turn_changes_cross_without_diffs,an_appended_snapshot_drops_the_superseded_ones_diffs_in_the_same_commit,history_windows_are_byte_budgeted, thetool_callhelper, pipe.rslegacy_files_start_process; ui/storemerged_windows_visits_and_summaries_apply_as_changes,a_full_remote_device_attributes_only_space_members, therecordedhelper. Fixture-onlyfailed: false: thesession_statushelpers in shell.rs and store/snapshots.rs.Unchanged from main after this revision: the agent adapter tests (codex steer id included),
provider_native_subagent_events_create_and_feed_read_only_mirror_session,mux_clients_target_independent_drafts_and_receive_only_their_session_tail,clients_opening_a_cold_thread_through_the_mux_get_each_record_once, and the composer queue test.Merge Danger
Door: two-way
Logs and terminal output are kept; Undo never restores providers, watches or exact timestamps. Older binaries ignore the new metadata fields and message origins; rolling back restores idle auto-archive with its default settings.
Blast Radius: lifecycle
On first start the default three-day rule settles every unblocked top-level thread whose last activity is older than three days, including non-resident ones: 150 of 942 on the measured profile. The cold projection reads each unarchived thread's log tail once at startup (about 4.7 s in a debug build on that profile) off the host mailbox; a thread whose log has no message or run records is read whole and never ages. Busy or uninspectable terminals stay open. The PR rule and grace (#532), pins and arrangement (#528), and Agents/roster (#637) are not in this PR.