Fix deletion bugs: scope escalation, checkpoint recovery, progress UX#26
Merged
Fix deletion bugs: scope escalation, checkpoint recovery, progress UX#26
Conversation
When viewing a sub-aggregate (e.g., time periods within a specific sender), pressing 'd' to stage for deletion only applied the sub-aggregate filter (time period) and ignored the parent drill-down filter (sender). This caused far more messages to be staged than intended. Pass the drill-down filter through to StageForDeletion and use it as the base filter when resolving Gmail IDs, so both parent and child filters are combined. Fixes #8 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, delete-staged would attempt the API call with the existing token, get a 403 insufficient scope error, and only then offer to re-authorize. This was confusing because users expected the tool to request deletion permissions upfront. Now tokens are saved with scope metadata, and delete-staged checks proactively whether the token has the required mail.google.com scope before making any API calls. Legacy tokens without scope metadata fall back to the existing reactive detection. Also extracts the scope escalation prompt into a shared function used by both the proactive and reactive paths. Fixes #11 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…wing The executor's ExecuteBatch and Execute methods were catching 403 insufficient scope errors and silently counting them as failures. This prevented the scope escalation prompt in delete-staged from ever triggering, since the executor returned nil (success with failures) instead of propagating the scope error. Now both methods detect scope errors and return them immediately, allowing the command layer to prompt for re-authorization. Also adds .githooks/post-commit to .gitignore. Fixes #11 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Three related fixes for deletion reliability: 1. ExecuteBatch's fallback-to-individual loop now detects scope errors and returns immediately instead of counting them as generic failures. Both ExecuteBatch and Execute save checkpoints before returning on scope errors so the resume position is correct. 2. CancelManifest now works for both pending and in-progress manifests, not just pending. This lets users clear corrupted in-progress batches. 3. cancel-deletion supports --all to clear all pending and in-progress batches at once, useful for recovering from persistent failures. Also adds .githooks/post-commit to .gitignore. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Set Content-Type: application/json header on API requests with a body, fixing Gmail batchDelete returning 400 "No message ids specified". Also add per-message progress reporting in the individual-delete fallback so the CLI no longer appears frozen. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Return nil on user cancel in promptScopeEscalation for clean exit (#4105) - Fix CancelManifest to surface real errors, only continue on NotExist (#4108) - Fix fallback loop LastProcessedIndex to track per-item position (#4108) - Surface list errors in cancel-deletion --all (#4108) - Add tests for scope error propagation in Execute, ExecuteBatch, and fallback paths (#4107, #4108) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ch-id promptScopeEscalation now returns errUserCanceled instead of nil so the proactive caller doesn't proceed with the old token. The proactive path maps it to a clean exit; the reactive path already exited cleanly. Also reject --all combined with a positional batch-id argument in cancel-deletion, and add tests for canceling in-progress manifests. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Show initial 0% progress line on start so the CLI doesn't appear frozen. Add ETA estimate to the progress line. Log each batch before the API call so users see activity during long-running batch deletes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Only overwrite SourceID from accountFilter when non-nil, preserving drillFilter's account context in deletion staging (#4115 high) - Surface non-cancel errors from promptScopeEscalation in reactive path instead of swallowing them (#4114 medium) - Log manifest.Save errors on scope error checkpoint paths (#4115 low) - Add HasScopeMetadata() for reliable legacy token detection instead of using HasScope(gmail.readonly) as proxy (#4114 low) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…Batch ExecuteBatch now tracks FailedIDs (like Execute already did) and retries them via individual deletes on resume instead of carrying forward stale failure counts. This handles the case where a previous run failed due to a transient issue (e.g. missing Content-Type header, scope errors) and left a checkpoint with failed IDs that should be retried. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
On scope error during retry, save only unattempted + already-failed IDs (not the full original list) so succeeded IDs aren't retried and double-counted. Set Failed = len(FailedIDs) for internal consistency. Add test for scope error mid-retry verifying correct checkpoint state. Add corrupt token file test for HasScopeMetadata. Check json.Marshal errors in tests. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace raw INFO log lines during deletion with Debug level so they don't collide with the progress bar. Redesign progress display with a visual progress bar, ETA, and elapsed time. Show batch description instead of ID in the execution header. Before: Executing: 20260202-202547-Senders-order-update (1879 messages) time=... level=INFO msg="executing batch deletion" ... Progress: 0/1879 (0.0%) ...time=... level=INFO msg="deleting batch" ... After: [1/2] Senders-order-update@example.com (1879 messages) [########----------------------] 25.0% 475/1879 3m12s remaining Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
On non-TTY outputs (pipes, CI, redirected files), ANSI sequences like \033[K render as raw characters. Detect TTY via os.Stdout.Stat() and fall back to plain newline-separated progress on non-terminals. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
OnStart now calls OnProgress(0,0,0) so users see the progress bar at 0% right away instead of a blank line while the first batch is being sent to the API. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
OnProgress now returns early when total <= 0, preventing division by zero that would produce NaN% output. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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
Fixes #8, #11. Comprehensive fixes to the deletion system covering OAuth scope handling, checkpoint/resume robustness, and CLI output quality.
OAuth scope escalation
When a user runs
delete-stagedfor the first time, the existing OAuth token typically only hasgmail.readonly+gmail.modifyscopes. The deletion system now:Proactively detects insufficient scopes before attempting any API calls by checking stored token scope metadata
Displays a clear permission upgrade prompt explaining what's needed:
For trash (non-batch) deletion, a simpler variant is shown requesting
gmail.modifyscope.Canceling is clean: answering "N" returns to the prompt with no error, using a sentinel
errUserCancelederrorReactive fallback: if the proactive check can't run (legacy tokens without scope metadata), scope errors from the API are caught and the same prompt is shown
Legacy tokens (saved before scope tracking) are detected via
HasScopeMetadata()and handled gracefullyCheckpoint and resume robustness
ExecuteBatch(previously only in single-messageExecute)FailedIDs, those IDs are retried before continuing with remaining messagesCancelManifestnow surfaces real I/O errors instead of silently continuingLastProcessedIndexper-message instead of per-batchstartIndexvaluesTUI drill-down context fix
SourceIDoverwrite bug where "All Accounts" filter would clear the drill-down filter's source, causing deletion staging to ignore the current subgroup viewCLI output
Test plan
delete-stagedwith insufficient scopes → shows permission upgrade promptmake test && make lintpassdelete-staged 2>&1 | cat)🤖 Generated with Claude Code