feat(tasks): auto-archive or delete tasks whose PR has been merged#2688
Open
SpielerNogard wants to merge 19 commits into
Open
feat(tasks): auto-archive or delete tasks whose PR has been merged#2688SpielerNogard wants to merge 19 commits into
SpielerNogard wants to merge 19 commits into
Conversation
…ncy guard and input debounce - Scope candidate PRs to the task project's known remotes (project_remotes) to prevent cross-repo branch-name collisions from triggering archive/delete. - Guard runOnce() with a _running flag so overlapping ticks no-op. - Replace raw sql`IS NULL` with isNull(tasks.archivedAt) for consistency. - Debounce the settings duration <Input> by 300ms with commit-on-blur, matching the design spec.
When a repository has any merged PR with merged_at IS NULL (legacy rows from before the column was added), reset its full/incremental sync cursors on the next sync() call so a fresh full sync re-pulls and populates the field. Cached per process so the cheap DB probe runs at most once per repo per app lifetime. Defensive try/catch ensures a probe failure never breaks the sync path.
…erged_at Replaces the 'time since PR merge' trigger with 'time since last task interaction' so an actively-worked-on task isn't archived just because its PR happened to merge a while ago. lastInteractedAt is the same timestamp the sidebar already displays, falling back to updatedAt when the task has never been interacted with. - Scheduler joins on tasks.lastInteractedAt (COALESCEd with updatedAt) instead of pullRequests.merged_at; status='merged' still required. - Removes the pullRequests.merged_at column (migration 0017) and all associated GitHub-sync, GraphQL fragment, and backfill code. - Migration 0016 still ships (adds auto_cleanup_opt_out); 0017 cleans up merged_at before the feature reaches users.
Without this log it was impossible to tell from the log file whether the scheduler was running and how many tasks it considered each tick.
This reverts commit d67b131.
8babd9e to
c540ab2
Compare
…restore When the user manually restores an archived task, treat the restore as fresh activity by bumping lastInteractedAt. This subsumes the opt-out flag: the scheduler already excludes tasks whose lastInteractedAt is within the configured delay. A user who intentionally keeps a restored task around will interact with it again before the delay expires; one who restores and forgets has effectively asked for re-cleanup. - Removes tasks.auto_cleanup_opt_out column and the two unmerged migrations (0016 add, 0017 drop merged_at) that the previous design required. Branch now ships with zero schema changes. - restoreTask bumps lastInteractedAt instead of setting opt-out. - Scheduler drops the opt-out filter. - Updated tests and legacy-port fixture schemas.
db:fixtures regenerates these files non-deterministically (SQLite page allocation, hidden timestamps) even when the schema is unchanged. The regenerated bytes pass the migration tests just as upstream's bytes do, so keep upstream's bytes to avoid noise in the diff.
Contributor
Greptile SummaryThis PR adds automatic cleanup for tasks whose linked pull request has merged. The main changes are:
Confidence Score: 4/5The cleanup path can act on unintended task rows and the delay UI can save a shorter delay than the user entered.
auto-cleanup-scheduler.ts, index.ts, TaskSettingsRows.tsx, telemetry sanitizer
|
| Filename | Overview |
|---|---|
| apps/emdash-desktop/src/main/core/tasks/auto-cleanup-scheduler.ts | Adds the cleanup loop, candidate query, and archive/delete dispatch; task scoping and delete options need attention. |
| apps/emdash-desktop/src/main/index.ts | Starts the scheduler during app boot before settings initialization has completed. |
| apps/emdash-desktop/src/renderer/features/settings/components/TaskSettingsRows.tsx | Adds the settings row; the delay input can persist the wrong value when the unit changes before debounce completes. |
| apps/emdash-desktop/src/main/core/settings/schema.ts | Adds validation for the new cleanup settings. |
| apps/emdash-desktop/src/main/core/settings/settings-registry.ts | Adds opt-in defaults for the cleanup settings. |
| apps/emdash-desktop/src/main/core/tasks/operations/restoreTask.ts | Updates restored tasks with a fresh interaction timestamp. |
| apps/emdash-desktop/src/renderer/lib/duration.ts | Adds helpers for converting cleanup delays between milliseconds and UI units. |
| apps/emdash-desktop/src/shared/telemetry.ts | Adds the typed auto-cleanup telemetry event. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
UI[General settings row] --> Settings[Task cleanup settings]
Settings --> Scheduler[AutoCleanupScheduler]
Scheduler --> Query[Find merged-PR task candidates]
Query --> Decision{Cleanup action}
Decision -->|archive| Archive[archiveTask]
Decision -->|delete| Delete[deleteTask]
Archive --> Telemetry[task_auto_cleaned_up]
Delete --> Telemetry
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
UI[General settings row] --> Settings[Task cleanup settings]
Settings --> Scheduler[AutoCleanupScheduler]
Scheduler --> Query[Find merged-PR task candidates]
Query --> Decision{Cleanup action}
Decision -->|archive| Archive[archiveTask]
Decision -->|delete| Delete[deleteTask]
Archive --> Telemetry[task_auto_cleaned_up]
Delete --> Telemetry
Prompt To Fix All With AI
Fix the following 5 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 5
apps/emdash-desktop/src/main/index.ts:135
**Scheduler Runs Before Settings**
`initialize()` schedules an immediate microtask, but this call happens before `appSettingsService.initialize()` later in startup. On app launch, the first cleanup tick can read settings before persisted settings and defaults are loaded, so it can fail or skip a user-enabled cleanup until the next interval.
### Issue 2 of 5
apps/emdash-desktop/src/main/core/tasks/auto-cleanup-scheduler.ts:86-106
**System Tasks Become Candidates**
This query selects every non-archived task row with a workspace branch matching a merged PR, but it does not limit the result to normal user tasks. If an `automation-run` or other system task has a workspace on that branch, enabling auto-cleanup can archive or delete that row and trigger the normal worktree cleanup path for a task the setting did not mean to manage.
### Issue 3 of 5
apps/emdash-desktop/src/main/core/tasks/auto-cleanup-scheduler.ts:135
**Delete Always Removes Worktree**
The auto-delete path always passes `deleteWorktree: true`, while the new setting only exposes the optional branch deletion choice. When a user enables delete mode to clean up task records after merge, this path also removes the worktree even if they left the branch checkbox off, which can delete local working files earlier than the UI implies.
### Issue 4 of 5
apps/emdash-desktop/src/renderer/features/settings/components/TaskSettingsRows.tsx:287-293
**Unit Switch Drops Draft**
`updateUnit` clears the pending debounce and converts the last persisted `value`, not the number currently typed in `draftValue`. From the default `1 day`, typing `2` and immediately selecting `Hours` saves `1 hour` instead of `2 hours`, so cleanup can run much earlier than the visible input the user just entered.
### Issue 5 of 5
apps/emdash-desktop/src/shared/telemetry.ts:106-111
**Delay Field Is Stripped**
The new event type includes `delay_ms`, and the scheduler sends it, but the telemetry sanitizer's allowed property set does not include that key. Captured `task_auto_cleaned_up` events therefore lose the configured delay, so cleanup telemetry cannot answer which delay caused an automatic action.
Reviews (1): Last reviewed commit: "test(fixtures): restore upstream fixture..." | Re-trigger Greptile
- main/index.ts: move autoCleanupScheduler.initialize() after appSettingsService.initialize() so the first tick never reads the settings service before it has loaded persisted state. - auto-cleanup-scheduler: restrict candidate query to tasks.type='task' so automation-run rows with a matching merged-PR branch are never picked up by user-task cleanup. - TaskSettingsRows: when the user changes the unit while a number is still in the input (not yet committed), use the typed draft instead of the persisted value, so the saved delay matches what the user sees. - TaskSettingsRows: clarify in the row description that 'Delete' also removes the worktree; only the branch deletion is optional. - telemetry: add 'delay_ms' to the allowed-properties allowlist and treat it as a bounded duration; without this the sanitizer was silently stripping the field from task_auto_cleaned_up events.
Contributor
|
Thanks for opening this PR, @SpielerNogard. This feature makes a lot of sense, especially as a separate setting. We’ll take a closer look and get back to you with proper feedback soon. Thanks again, also for your engagement here lately. |
Contributor
Author
|
Sure take your time. And all good if I can contribute to a tool i use every day im happy |
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.
Description
Adds an opt-in global setting under Settings → General → Auto-cleanup merged tasks that periodically archives or deletes tasks whose linked pull request has been merged, after the task has been idle for a user-configurable delay.
lastInteractedAt, so the user's restore counts as fresh activity and the task is not re-archived until at least the configured delay has passed.How it works. A new singleton
AutoCleanupSchedulerticks every 5 minutes. Each tick reads thetaskssettings (no-op if disabled), then selects candidates wheretasks.archivedAt IS NULLAND the linked PR is merged AND the task's last activity (COALESCE(lastInteractedAt, updatedAt)) is older than the configured delay. PR↔task matching is scoped viaproject_remotesto avoid cross-repo branch-name collisions. Each candidate is run through the existingarchiveTask/deleteTaskoperations. Per-task error isolation and a re-entrancy guard so overlapping ticks no-op.The "time since last task activity" trigger means an actively-worked-on task isn't archived just because its PR happened to merge a while ago, and gives the user a natural way to keep a restored task around — simply interact with it before the delay expires.
Touched areas:
apps/emdash-desktop/src/main/core/tasks/auto-cleanup-scheduler.ts+ unit tests.tasks(zod-validated, defaults wired):autoCleanupMergedEnabled,autoCleanupMergedAction,autoCleanupMergedDeleteBranch,autoCleanupMergedDelayMs.restoreTaskbumpslastInteractedAtso restored tasks are not immediately re-archived on the next tick.task_auto_cleaned_up.durationToMs/msToDuration) for the settings UI.AutoCleanupMergedTasksRowin the General settings tab, with a 300 ms debounced number input.main/index.tsinitialize,main/app/shutdown.tsdispose).Related issues
None.
Testing
pnpm run format— cleanpnpm run lint— clean (no new warnings)pnpm run typecheck— cleanpnpm run test— 2081/2081 unit tests pass (2 Playwright browser test files fail at runner setup due to local Playwright install; unrelated to this change)pnpm --dir apps/emdash-desktop run test:migrations— clean1 houragainst a production-DB snapshot, confirmed a known-merged task is archived on the next tick (< 5 min); manually unarchived the task and confirmed it is not re-archived on subsequent ticks (becauselastInteractedAtwas just bumped).Screenshot/Recording (if applicable)
Before cluttered workspaces

New setting in general settings

Enough space for new ideas :)

Checklist