Skip to content

fix(google-tasks): handle deleted task list (404) instead of throwing - #230

Merged
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404
Jun 25, 2026
Merged

fix(google-tasks): handle deleted task list (404) instead of throwing#230
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Problem

When a user deletes a Google Tasks list but its Plot channel stays enabled, every periodic poll (and the initial backfill) calls listTasks(listId) and gets a permanent 404 Task list not found. That error was thrown unhandled, so it:

  1. was captured by error tracking,
  2. was retried 3× by RUN_QUEUE (pointless — the list is permanently gone), then
  3. dead-lettered (RUN_QUEUE dead-letter:),

repeating every hour per affected user. This was the dominant recent contributor to the catch-all handleTwistOperation PostHog issue 019ed581 (the issue title is misleading — it's a poly-error bucket; the most-recent occurrences were the Google Tasks 404 + its downstream dead-letter).

Fix

  • api.ts — introduce a typed GoogleTasksApiError carrying the HTTP status (message format preserved verbatim for log/grouping back-compat) plus an isNotFoundError() helper. request() throws the typed error.
  • sync.tssyncBatchFn and periodicSyncBatchFn now catch a 404 and tear the channel down via the connector's own onChannelDisabledFn (cancel the poll, clear state, archive the list's links) and return { done: true } instead of throwing. onLinkUpdatedFn swallows a 404 write-back (the task/list is gone). Non-404 errors still rethrow.

Treating a deleted list like a disable (archiving its synced tasks) matches existing disable semantics — the tasks no longer exist in the source.

This logic is shared by both the standalone GoogleTasks connector and the combined google connector (both import these functions and schedule the poll under poll:<listId>, routing cancelScheduledTask through the host), so one fix covers both.

Tests

Adds a vitest harness to the connector (mirroring the google connector's setup) with 5 tests:

  • syncBatchFn / periodicSyncBatchFn tear the channel down on a 404 (cancel poll, archive links, clear state) without throwing
  • both still throw on a non-404 (genuine) failure
  • onLinkUpdatedFn swallows a 404 but rethrows non-404

tsc build ✓ · plot lint ✓ · 5/5 tests ✓

Notes

  • Connector-only change → no changeset (only twister/ requires one).
  • PostHog issue 019ed581 was intentionally not marked resolved (it's a catch-all sharing one fingerprint with still-active DB-pool-timeout/OOM errors). A grouping rule (019f0002-8512-0000-b9ad-e4bffbc85ea4, $exception_values icontains "Google Tasks API error") now de-collapses Google Tasks errors into their own issue so this fix can be verified post-deploy.

🤖 Generated with Claude Code

KrisBraunand others added 2 commits June 25, 2026 14:32
When a user deletes a Google Tasks list but its Plot channel stays
enabled, every periodic poll (and the initial backfill) calls
`listTasks(listId)` and gets a permanent `404 Task list not found`.
That error was thrown unhandled, so it was captured by error tracking,
retried 3x by RUN_QUEUE (pointless — the list is gone), then
dead-lettered ("RUN_QUEUE dead-letter:") — repeating every hour per
affected user. This was the dominant recent contributor to the
catch-all handleTwistOperation PostHog issue 019ed581.
Fix:
- api.ts: introduce a typed `GoogleTasksApiError` carrying the HTTP
`status` (message format preserved verbatim) plus an `isNotFoundError`
helper. `request()` throws the typed error.
- sync.ts: `syncBatchFn` and `periodicSyncBatchFn` now catch a 404 and
tear the channel down via the connector's own `onChannelDisabledFn`
(cancel the poll, clear state, archive the list's links) and return
`{ done: true }` instead of throwing. `onLinkUpdatedFn` swallows a 404
write-back (the task/list is gone). Non-404 errors still rethrow.
This is shared by both the standalone GoogleTasks connector and the
combined Google connector (both import these functions and schedule the
poll under `poll:<listId>`), so one fix covers both.
Adds a vitest harness (mirroring the google connector) with tests
covering teardown-on-404, write-back swallow, and non-404 rethrow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the vitest@^2.1.8 entry for connectors/google-tasks so
`pnpm install --frozen-lockfile` passes in CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@KrisBraun
KrisBraun merged commit 9aced82 into mainJun 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(google-tasks): handle deleted task list (404) instead of throwing by KrisBraun · Pull Request #230 · plotday/plot · GitHub
Skip to content

fix(google-tasks): handle deleted task list (404) instead of throwing - #230

Merged
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404
Jun 25, 2026
Merged

fix(google-tasks): handle deleted task list (404) instead of throwing#230
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Problem

When a user deletes a Google Tasks list but its Plot channel stays enabled, every periodic poll (and the initial backfill) calls listTasks(listId) and gets a permanent 404 Task list not found. That error was thrown unhandled, so it:

  1. was captured by error tracking,
  2. was retried 3× by RUN_QUEUE (pointless — the list is permanently gone), then
  3. dead-lettered (RUN_QUEUE dead-letter:),

repeating every hour per affected user. This was the dominant recent contributor to the catch-all handleTwistOperation PostHog issue 019ed581 (the issue title is misleading — it's a poly-error bucket; the most-recent occurrences were the Google Tasks 404 + its downstream dead-letter).

Fix

  • api.ts — introduce a typed GoogleTasksApiError carrying the HTTP status (message format preserved verbatim for log/grouping back-compat) plus an isNotFoundError() helper. request() throws the typed error.
  • sync.tssyncBatchFn and periodicSyncBatchFn now catch a 404 and tear the channel down via the connector's own onChannelDisabledFn (cancel the poll, clear state, archive the list's links) and return { done: true } instead of throwing. onLinkUpdatedFn swallows a 404 write-back (the task/list is gone). Non-404 errors still rethrow.

Treating a deleted list like a disable (archiving its synced tasks) matches existing disable semantics — the tasks no longer exist in the source.

This logic is shared by both the standalone GoogleTasks connector and the combined google connector (both import these functions and schedule the poll under poll:<listId>, routing cancelScheduledTask through the host), so one fix covers both.

Tests

Adds a vitest harness to the connector (mirroring the google connector's setup) with 5 tests:

  • syncBatchFn / periodicSyncBatchFn tear the channel down on a 404 (cancel poll, archive links, clear state) without throwing
  • both still throw on a non-404 (genuine) failure
  • onLinkUpdatedFn swallows a 404 but rethrows non-404

tsc build ✓ · plot lint ✓ · 5/5 tests ✓

Notes

  • Connector-only change → no changeset (only twister/ requires one).
  • PostHog issue 019ed581 was intentionally not marked resolved (it's a catch-all sharing one fingerprint with still-active DB-pool-timeout/OOM errors). A grouping rule (019f0002-8512-0000-b9ad-e4bffbc85ea4, $exception_values icontains "Google Tasks API error") now de-collapses Google Tasks errors into their own issue so this fix can be verified post-deploy.

🤖 Generated with Claude Code

KrisBraunand others added 2 commits June 25, 2026 14:32
When a user deletes a Google Tasks list but its Plot channel stays
enabled, every periodic poll (and the initial backfill) calls
`listTasks(listId)` and gets a permanent `404 Task list not found`.
That error was thrown unhandled, so it was captured by error tracking,
retried 3x by RUN_QUEUE (pointless — the list is gone), then
dead-lettered ("RUN_QUEUE dead-letter:") — repeating every hour per
affected user. This was the dominant recent contributor to the
catch-all handleTwistOperation PostHog issue 019ed581.
Fix:
- api.ts: introduce a typed `GoogleTasksApiError` carrying the HTTP
`status` (message format preserved verbatim) plus an `isNotFoundError`
helper. `request()` throws the typed error.
- sync.ts: `syncBatchFn` and `periodicSyncBatchFn` now catch a 404 and
tear the channel down via the connector's own `onChannelDisabledFn`
(cancel the poll, clear state, archive the list's links) and return
`{ done: true }` instead of throwing. `onLinkUpdatedFn` swallows a 404
write-back (the task/list is gone). Non-404 errors still rethrow.
This is shared by both the standalone GoogleTasks connector and the
combined Google connector (both import these functions and schedule the
poll under `poll:<listId>`), so one fix covers both.
Adds a vitest harness (mirroring the google connector) with tests
covering teardown-on-404, write-back swallow, and non-404 rethrow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the vitest@^2.1.8 entry for connectors/google-tasks so
`pnpm install --frozen-lockfile` passes in CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@KrisBraun
KrisBraun merged commit 9aced82 into mainJun 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(google-tasks): handle deleted task list (404) instead of throwing by KrisBraun · Pull Request #230 · plotday/plot · GitHub
Skip to content

fix(google-tasks): handle deleted task list (404) instead of throwing - #230

Merged
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404
Jun 25, 2026
Merged

fix(google-tasks): handle deleted task list (404) instead of throwing#230
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Problem

When a user deletes a Google Tasks list but its Plot channel stays enabled, every periodic poll (and the initial backfill) calls listTasks(listId) and gets a permanent 404 Task list not found. That error was thrown unhandled, so it:

  1. was captured by error tracking,
  2. was retried 3× by RUN_QUEUE (pointless — the list is permanently gone), then
  3. dead-lettered (RUN_QUEUE dead-letter:),

repeating every hour per affected user. This was the dominant recent contributor to the catch-all handleTwistOperation PostHog issue 019ed581 (the issue title is misleading — it's a poly-error bucket; the most-recent occurrences were the Google Tasks 404 + its downstream dead-letter).

Fix

  • api.ts — introduce a typed GoogleTasksApiError carrying the HTTP status (message format preserved verbatim for log/grouping back-compat) plus an isNotFoundError() helper. request() throws the typed error.
  • sync.tssyncBatchFn and periodicSyncBatchFn now catch a 404 and tear the channel down via the connector's own onChannelDisabledFn (cancel the poll, clear state, archive the list's links) and return { done: true } instead of throwing. onLinkUpdatedFn swallows a 404 write-back (the task/list is gone). Non-404 errors still rethrow.

Treating a deleted list like a disable (archiving its synced tasks) matches existing disable semantics — the tasks no longer exist in the source.

This logic is shared by both the standalone GoogleTasks connector and the combined google connector (both import these functions and schedule the poll under poll:<listId>, routing cancelScheduledTask through the host), so one fix covers both.

Tests

Adds a vitest harness to the connector (mirroring the google connector's setup) with 5 tests:

  • syncBatchFn / periodicSyncBatchFn tear the channel down on a 404 (cancel poll, archive links, clear state) without throwing
  • both still throw on a non-404 (genuine) failure
  • onLinkUpdatedFn swallows a 404 but rethrows non-404

tsc build ✓ · plot lint ✓ · 5/5 tests ✓

Notes

  • Connector-only change → no changeset (only twister/ requires one).
  • PostHog issue 019ed581 was intentionally not marked resolved (it's a catch-all sharing one fingerprint with still-active DB-pool-timeout/OOM errors). A grouping rule (019f0002-8512-0000-b9ad-e4bffbc85ea4, $exception_values icontains "Google Tasks API error") now de-collapses Google Tasks errors into their own issue so this fix can be verified post-deploy.

🤖 Generated with Claude Code

KrisBraunand others added 2 commits June 25, 2026 14:32
When a user deletes a Google Tasks list but its Plot channel stays
enabled, every periodic poll (and the initial backfill) calls
`listTasks(listId)` and gets a permanent `404 Task list not found`.
That error was thrown unhandled, so it was captured by error tracking,
retried 3x by RUN_QUEUE (pointless — the list is gone), then
dead-lettered ("RUN_QUEUE dead-letter:") — repeating every hour per
affected user. This was the dominant recent contributor to the
catch-all handleTwistOperation PostHog issue 019ed581.
Fix:
- api.ts: introduce a typed `GoogleTasksApiError` carrying the HTTP
`status` (message format preserved verbatim) plus an `isNotFoundError`
helper. `request()` throws the typed error.
- sync.ts: `syncBatchFn` and `periodicSyncBatchFn` now catch a 404 and
tear the channel down via the connector's own `onChannelDisabledFn`
(cancel the poll, clear state, archive the list's links) and return
`{ done: true }` instead of throwing. `onLinkUpdatedFn` swallows a 404
write-back (the task/list is gone). Non-404 errors still rethrow.
This is shared by both the standalone GoogleTasks connector and the
combined Google connector (both import these functions and schedule the
poll under `poll:<listId>`), so one fix covers both.
Adds a vitest harness (mirroring the google connector) with tests
covering teardown-on-404, write-back swallow, and non-404 rethrow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the vitest@^2.1.8 entry for connectors/google-tasks so
`pnpm install --frozen-lockfile` passes in CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@KrisBraun
KrisBraun merged commit 9aced82 into mainJun 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(google-tasks): handle deleted task list (404) instead of throwing by KrisBraun · Pull Request #230 · plotday/plot · GitHub
Skip to content

fix(google-tasks): handle deleted task list (404) instead of throwing - #230

Merged
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404
Jun 25, 2026
Merged

fix(google-tasks): handle deleted task list (404) instead of throwing#230
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Problem

When a user deletes a Google Tasks list but its Plot channel stays enabled, every periodic poll (and the initial backfill) calls listTasks(listId) and gets a permanent 404 Task list not found. That error was thrown unhandled, so it:

  1. was captured by error tracking,
  2. was retried 3× by RUN_QUEUE (pointless — the list is permanently gone), then
  3. dead-lettered (RUN_QUEUE dead-letter:),

repeating every hour per affected user. This was the dominant recent contributor to the catch-all handleTwistOperation PostHog issue 019ed581 (the issue title is misleading — it's a poly-error bucket; the most-recent occurrences were the Google Tasks 404 + its downstream dead-letter).

Fix

  • api.ts — introduce a typed GoogleTasksApiError carrying the HTTP status (message format preserved verbatim for log/grouping back-compat) plus an isNotFoundError() helper. request() throws the typed error.
  • sync.tssyncBatchFn and periodicSyncBatchFn now catch a 404 and tear the channel down via the connector's own onChannelDisabledFn (cancel the poll, clear state, archive the list's links) and return { done: true } instead of throwing. onLinkUpdatedFn swallows a 404 write-back (the task/list is gone). Non-404 errors still rethrow.

Treating a deleted list like a disable (archiving its synced tasks) matches existing disable semantics — the tasks no longer exist in the source.

This logic is shared by both the standalone GoogleTasks connector and the combined google connector (both import these functions and schedule the poll under poll:<listId>, routing cancelScheduledTask through the host), so one fix covers both.

Tests

Adds a vitest harness to the connector (mirroring the google connector's setup) with 5 tests:

  • syncBatchFn / periodicSyncBatchFn tear the channel down on a 404 (cancel poll, archive links, clear state) without throwing
  • both still throw on a non-404 (genuine) failure
  • onLinkUpdatedFn swallows a 404 but rethrows non-404

tsc build ✓ · plot lint ✓ · 5/5 tests ✓

Notes

  • Connector-only change → no changeset (only twister/ requires one).
  • PostHog issue 019ed581 was intentionally not marked resolved (it's a catch-all sharing one fingerprint with still-active DB-pool-timeout/OOM errors). A grouping rule (019f0002-8512-0000-b9ad-e4bffbc85ea4, $exception_values icontains "Google Tasks API error") now de-collapses Google Tasks errors into their own issue so this fix can be verified post-deploy.

🤖 Generated with Claude Code

KrisBraunand others added 2 commits June 25, 2026 14:32
When a user deletes a Google Tasks list but its Plot channel stays
enabled, every periodic poll (and the initial backfill) calls
`listTasks(listId)` and gets a permanent `404 Task list not found`.
That error was thrown unhandled, so it was captured by error tracking,
retried 3x by RUN_QUEUE (pointless — the list is gone), then
dead-lettered ("RUN_QUEUE dead-letter:") — repeating every hour per
affected user. This was the dominant recent contributor to the
catch-all handleTwistOperation PostHog issue 019ed581.
Fix:
- api.ts: introduce a typed `GoogleTasksApiError` carrying the HTTP
`status` (message format preserved verbatim) plus an `isNotFoundError`
helper. `request()` throws the typed error.
- sync.ts: `syncBatchFn` and `periodicSyncBatchFn` now catch a 404 and
tear the channel down via the connector's own `onChannelDisabledFn`
(cancel the poll, clear state, archive the list's links) and return
`{ done: true }` instead of throwing. `onLinkUpdatedFn` swallows a 404
write-back (the task/list is gone). Non-404 errors still rethrow.
This is shared by both the standalone GoogleTasks connector and the
combined Google connector (both import these functions and schedule the
poll under `poll:<listId>`), so one fix covers both.
Adds a vitest harness (mirroring the google connector) with tests
covering teardown-on-404, write-back swallow, and non-404 rethrow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the vitest@^2.1.8 entry for connectors/google-tasks so
`pnpm install --frozen-lockfile` passes in CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@KrisBraun
KrisBraun merged commit 9aced82 into mainJun 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix(google-tasks): handle deleted task list (404) instead of throwing by KrisBraun · Pull Request #230 · plotday/plot · GitHub
Skip to content

fix(google-tasks): handle deleted task list (404) instead of throwing - #230

Merged
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404
Jun 25, 2026
Merged

fix(google-tasks): handle deleted task list (404) instead of throwing#230
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Problem

When a user deletes a Google Tasks list but its Plot channel stays enabled, every periodic poll (and the initial backfill) calls listTasks(listId) and gets a permanent 404 Task list not found. That error was thrown unhandled, so it:

  1. was captured by error tracking,
  2. was retried 3× by RUN_QUEUE (pointless — the list is permanently gone), then
  3. dead-lettered (RUN_QUEUE dead-letter:),

repeating every hour per affected user. This was the dominant recent contributor to the catch-all handleTwistOperation PostHog issue 019ed581 (the issue title is misleading — it's a poly-error bucket; the most-recent occurrences were the Google Tasks 404 + its downstream dead-letter).

Fix

  • api.ts — introduce a typed GoogleTasksApiError carrying the HTTP status (message format preserved verbatim for log/grouping back-compat) plus an isNotFoundError() helper. request() throws the typed error.
  • sync.tssyncBatchFn and periodicSyncBatchFn now catch a 404 and tear the channel down via the connector's own onChannelDisabledFn (cancel the poll, clear state, archive the list's links) and return { done: true } instead of throwing. onLinkUpdatedFn swallows a 404 write-back (the task/list is gone). Non-404 errors still rethrow.

Treating a deleted list like a disable (archiving its synced tasks) matches existing disable semantics — the tasks no longer exist in the source.

This logic is shared by both the standalone GoogleTasks connector and the combined google connector (both import these functions and schedule the poll under poll:<listId>, routing cancelScheduledTask through the host), so one fix covers both.

Tests

Adds a vitest harness to the connector (mirroring the google connector's setup) with 5 tests:

  • syncBatchFn / periodicSyncBatchFn tear the channel down on a 404 (cancel poll, archive links, clear state) without throwing
  • both still throw on a non-404 (genuine) failure
  • onLinkUpdatedFn swallows a 404 but rethrows non-404

tsc build ✓ · plot lint ✓ · 5/5 tests ✓

Notes

  • Connector-only change → no changeset (only twister/ requires one).
  • PostHog issue 019ed581 was intentionally not marked resolved (it's a catch-all sharing one fingerprint with still-active DB-pool-timeout/OOM errors). A grouping rule (019f0002-8512-0000-b9ad-e4bffbc85ea4, $exception_values icontains "Google Tasks API error") now de-collapses Google Tasks errors into their own issue so this fix can be verified post-deploy.

🤖 Generated with Claude Code

KrisBraunand others added 2 commits June 25, 2026 14:32
When a user deletes a Google Tasks list but its Plot channel stays
enabled, every periodic poll (and the initial backfill) calls
`listTasks(listId)` and gets a permanent `404 Task list not found`.
That error was thrown unhandled, so it was captured by error tracking,
retried 3x by RUN_QUEUE (pointless — the list is gone), then
dead-lettered ("RUN_QUEUE dead-letter:") — repeating every hour per
affected user. This was the dominant recent contributor to the
catch-all handleTwistOperation PostHog issue 019ed581.
Fix:
- api.ts: introduce a typed `GoogleTasksApiError` carrying the HTTP
`status` (message format preserved verbatim) plus an `isNotFoundError`
helper. `request()` throws the typed error.
- sync.ts: `syncBatchFn` and `periodicSyncBatchFn` now catch a 404 and
tear the channel down via the connector's own `onChannelDisabledFn`
(cancel the poll, clear state, archive the list's links) and return
`{ done: true }` instead of throwing. `onLinkUpdatedFn` swallows a 404
write-back (the task/list is gone). Non-404 errors still rethrow.
This is shared by both the standalone GoogleTasks connector and the
combined Google connector (both import these functions and schedule the
poll under `poll:<listId>`), so one fix covers both.
Adds a vitest harness (mirroring the google connector) with tests
covering teardown-on-404, write-back swallow, and non-404 rethrow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the vitest@^2.1.8 entry for connectors/google-tasks so
`pnpm install --frozen-lockfile` passes in CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@KrisBraun
KrisBraun merged commit 9aced82 into mainJun 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(google-tasks): handle deleted task list (404) instead of throwing by KrisBraun · Pull Request #230 · plotday/plot · GitHub
Skip to content

fix(google-tasks): handle deleted task list (404) instead of throwing - #230

Merged
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404
Jun 25, 2026
Merged

fix(google-tasks): handle deleted task list (404) instead of throwing#230
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Problem

When a user deletes a Google Tasks list but its Plot channel stays enabled, every periodic poll (and the initial backfill) calls listTasks(listId) and gets a permanent 404 Task list not found. That error was thrown unhandled, so it:

  1. was captured by error tracking,
  2. was retried 3× by RUN_QUEUE (pointless — the list is permanently gone), then
  3. dead-lettered (RUN_QUEUE dead-letter:),

repeating every hour per affected user. This was the dominant recent contributor to the catch-all handleTwistOperation PostHog issue 019ed581 (the issue title is misleading — it's a poly-error bucket; the most-recent occurrences were the Google Tasks 404 + its downstream dead-letter).

Fix

  • api.ts — introduce a typed GoogleTasksApiError carrying the HTTP status (message format preserved verbatim for log/grouping back-compat) plus an isNotFoundError() helper. request() throws the typed error.
  • sync.tssyncBatchFn and periodicSyncBatchFn now catch a 404 and tear the channel down via the connector's own onChannelDisabledFn (cancel the poll, clear state, archive the list's links) and return { done: true } instead of throwing. onLinkUpdatedFn swallows a 404 write-back (the task/list is gone). Non-404 errors still rethrow.

Treating a deleted list like a disable (archiving its synced tasks) matches existing disable semantics — the tasks no longer exist in the source.

This logic is shared by both the standalone GoogleTasks connector and the combined google connector (both import these functions and schedule the poll under poll:<listId>, routing cancelScheduledTask through the host), so one fix covers both.

Tests

Adds a vitest harness to the connector (mirroring the google connector's setup) with 5 tests:

  • syncBatchFn / periodicSyncBatchFn tear the channel down on a 404 (cancel poll, archive links, clear state) without throwing
  • both still throw on a non-404 (genuine) failure
  • onLinkUpdatedFn swallows a 404 but rethrows non-404

tsc build ✓ · plot lint ✓ · 5/5 tests ✓

Notes

  • Connector-only change → no changeset (only twister/ requires one).
  • PostHog issue 019ed581 was intentionally not marked resolved (it's a catch-all sharing one fingerprint with still-active DB-pool-timeout/OOM errors). A grouping rule (019f0002-8512-0000-b9ad-e4bffbc85ea4, $exception_values icontains "Google Tasks API error") now de-collapses Google Tasks errors into their own issue so this fix can be verified post-deploy.

🤖 Generated with Claude Code

KrisBraunand others added 2 commits June 25, 2026 14:32
When a user deletes a Google Tasks list but its Plot channel stays
enabled, every periodic poll (and the initial backfill) calls
`listTasks(listId)` and gets a permanent `404 Task list not found`.
That error was thrown unhandled, so it was captured by error tracking,
retried 3x by RUN_QUEUE (pointless — the list is gone), then
dead-lettered ("RUN_QUEUE dead-letter:") — repeating every hour per
affected user. This was the dominant recent contributor to the
catch-all handleTwistOperation PostHog issue 019ed581.
Fix:
- api.ts: introduce a typed `GoogleTasksApiError` carrying the HTTP
`status` (message format preserved verbatim) plus an `isNotFoundError`
helper. `request()` throws the typed error.
- sync.ts: `syncBatchFn` and `periodicSyncBatchFn` now catch a 404 and
tear the channel down via the connector's own `onChannelDisabledFn`
(cancel the poll, clear state, archive the list's links) and return
`{ done: true }` instead of throwing. `onLinkUpdatedFn` swallows a 404
write-back (the task/list is gone). Non-404 errors still rethrow.
This is shared by both the standalone GoogleTasks connector and the
combined Google connector (both import these functions and schedule the
poll under `poll:<listId>`), so one fix covers both.
Adds a vitest harness (mirroring the google connector) with tests
covering teardown-on-404, write-back swallow, and non-404 rethrow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the vitest@^2.1.8 entry for connectors/google-tasks so
`pnpm install --frozen-lockfile` passes in CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@KrisBraun
KrisBraun merged commit 9aced82 into mainJun 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(google-tasks): handle deleted task list (404) instead of throwing by KrisBraun · Pull Request #230 · plotday/plot · GitHub
Skip to content

fix(google-tasks): handle deleted task list (404) instead of throwing - #230

Merged
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404
Jun 25, 2026
Merged

fix(google-tasks): handle deleted task list (404) instead of throwing#230
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Problem

When a user deletes a Google Tasks list but its Plot channel stays enabled, every periodic poll (and the initial backfill) calls listTasks(listId) and gets a permanent 404 Task list not found. That error was thrown unhandled, so it:

  1. was captured by error tracking,
  2. was retried 3× by RUN_QUEUE (pointless — the list is permanently gone), then
  3. dead-lettered (RUN_QUEUE dead-letter:),

repeating every hour per affected user. This was the dominant recent contributor to the catch-all handleTwistOperation PostHog issue 019ed581 (the issue title is misleading — it's a poly-error bucket; the most-recent occurrences were the Google Tasks 404 + its downstream dead-letter).

Fix

  • api.ts — introduce a typed GoogleTasksApiError carrying the HTTP status (message format preserved verbatim for log/grouping back-compat) plus an isNotFoundError() helper. request() throws the typed error.
  • sync.tssyncBatchFn and periodicSyncBatchFn now catch a 404 and tear the channel down via the connector's own onChannelDisabledFn (cancel the poll, clear state, archive the list's links) and return { done: true } instead of throwing. onLinkUpdatedFn swallows a 404 write-back (the task/list is gone). Non-404 errors still rethrow.

Treating a deleted list like a disable (archiving its synced tasks) matches existing disable semantics — the tasks no longer exist in the source.

This logic is shared by both the standalone GoogleTasks connector and the combined google connector (both import these functions and schedule the poll under poll:<listId>, routing cancelScheduledTask through the host), so one fix covers both.

Tests

Adds a vitest harness to the connector (mirroring the google connector's setup) with 5 tests:

  • syncBatchFn / periodicSyncBatchFn tear the channel down on a 404 (cancel poll, archive links, clear state) without throwing
  • both still throw on a non-404 (genuine) failure
  • onLinkUpdatedFn swallows a 404 but rethrows non-404

tsc build ✓ · plot lint ✓ · 5/5 tests ✓

Notes

  • Connector-only change → no changeset (only twister/ requires one).
  • PostHog issue 019ed581 was intentionally not marked resolved (it's a catch-all sharing one fingerprint with still-active DB-pool-timeout/OOM errors). A grouping rule (019f0002-8512-0000-b9ad-e4bffbc85ea4, $exception_values icontains "Google Tasks API error") now de-collapses Google Tasks errors into their own issue so this fix can be verified post-deploy.

🤖 Generated with Claude Code

KrisBraunand others added 2 commits June 25, 2026 14:32
When a user deletes a Google Tasks list but its Plot channel stays
enabled, every periodic poll (and the initial backfill) calls
`listTasks(listId)` and gets a permanent `404 Task list not found`.
That error was thrown unhandled, so it was captured by error tracking,
retried 3x by RUN_QUEUE (pointless — the list is gone), then
dead-lettered ("RUN_QUEUE dead-letter:") — repeating every hour per
affected user. This was the dominant recent contributor to the
catch-all handleTwistOperation PostHog issue 019ed581.
Fix:
- api.ts: introduce a typed `GoogleTasksApiError` carrying the HTTP
`status` (message format preserved verbatim) plus an `isNotFoundError`
helper. `request()` throws the typed error.
- sync.ts: `syncBatchFn` and `periodicSyncBatchFn` now catch a 404 and
tear the channel down via the connector's own `onChannelDisabledFn`
(cancel the poll, clear state, archive the list's links) and return
`{ done: true }` instead of throwing. `onLinkUpdatedFn` swallows a 404
write-back (the task/list is gone). Non-404 errors still rethrow.
This is shared by both the standalone GoogleTasks connector and the
combined Google connector (both import these functions and schedule the
poll under `poll:<listId>`), so one fix covers both.
Adds a vitest harness (mirroring the google connector) with tests
covering teardown-on-404, write-back swallow, and non-404 rethrow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the vitest@^2.1.8 entry for connectors/google-tasks so
`pnpm install --frozen-lockfile` passes in CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@KrisBraun
KrisBraun merged commit 9aced82 into mainJun 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix(google-tasks): handle deleted task list (404) instead of throwing by KrisBraun · Pull Request #230 · plotday/plot · GitHub
Skip to content

fix(google-tasks): handle deleted task list (404) instead of throwing - #230

Merged
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404
Jun 25, 2026
Merged

fix(google-tasks): handle deleted task list (404) instead of throwing#230
KrisBraun merged 2 commits into
mainfrom
fix/google-tasks-deleted-list-404

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Problem

When a user deletes a Google Tasks list but its Plot channel stays enabled, every periodic poll (and the initial backfill) calls listTasks(listId) and gets a permanent 404 Task list not found. That error was thrown unhandled, so it:

  1. was captured by error tracking,
  2. was retried 3× by RUN_QUEUE (pointless — the list is permanently gone), then
  3. dead-lettered (RUN_QUEUE dead-letter:),

repeating every hour per affected user. This was the dominant recent contributor to the catch-all handleTwistOperation PostHog issue 019ed581 (the issue title is misleading — it's a poly-error bucket; the most-recent occurrences were the Google Tasks 404 + its downstream dead-letter).

Fix

  • api.ts — introduce a typed GoogleTasksApiError carrying the HTTP status (message format preserved verbatim for log/grouping back-compat) plus an isNotFoundError() helper. request() throws the typed error.
  • sync.tssyncBatchFn and periodicSyncBatchFn now catch a 404 and tear the channel down via the connector's own onChannelDisabledFn (cancel the poll, clear state, archive the list's links) and return { done: true } instead of throwing. onLinkUpdatedFn swallows a 404 write-back (the task/list is gone). Non-404 errors still rethrow.

Treating a deleted list like a disable (archiving its synced tasks) matches existing disable semantics — the tasks no longer exist in the source.

This logic is shared by both the standalone GoogleTasks connector and the combined google connector (both import these functions and schedule the poll under poll:<listId>, routing cancelScheduledTask through the host), so one fix covers both.

Tests

Adds a vitest harness to the connector (mirroring the google connector's setup) with 5 tests:

  • syncBatchFn / periodicSyncBatchFn tear the channel down on a 404 (cancel poll, archive links, clear state) without throwing
  • both still throw on a non-404 (genuine) failure
  • onLinkUpdatedFn swallows a 404 but rethrows non-404

tsc build ✓ · plot lint ✓ · 5/5 tests ✓

Notes

  • Connector-only change → no changeset (only twister/ requires one).
  • PostHog issue 019ed581 was intentionally not marked resolved (it's a catch-all sharing one fingerprint with still-active DB-pool-timeout/OOM errors). A grouping rule (019f0002-8512-0000-b9ad-e4bffbc85ea4, $exception_values icontains "Google Tasks API error") now de-collapses Google Tasks errors into their own issue so this fix can be verified post-deploy.

🤖 Generated with Claude Code

KrisBraunand others added 2 commits June 25, 2026 14:32
When a user deletes a Google Tasks list but its Plot channel stays
enabled, every periodic poll (and the initial backfill) calls
`listTasks(listId)` and gets a permanent `404 Task list not found`.
That error was thrown unhandled, so it was captured by error tracking,
retried 3x by RUN_QUEUE (pointless — the list is gone), then
dead-lettered ("RUN_QUEUE dead-letter:") — repeating every hour per
affected user. This was the dominant recent contributor to the
catch-all handleTwistOperation PostHog issue 019ed581.
Fix:
- api.ts: introduce a typed `GoogleTasksApiError` carrying the HTTP
`status` (message format preserved verbatim) plus an `isNotFoundError`
helper. `request()` throws the typed error.
- sync.ts: `syncBatchFn` and `periodicSyncBatchFn` now catch a 404 and
tear the channel down via the connector's own `onChannelDisabledFn`
(cancel the poll, clear state, archive the list's links) and return
`{ done: true }` instead of throwing. `onLinkUpdatedFn` swallows a 404
write-back (the task/list is gone). Non-404 errors still rethrow.
This is shared by both the standalone GoogleTasks connector and the
combined Google connector (both import these functions and schedule the
poll under `poll:<listId>`), so one fix covers both.
Adds a vitest harness (mirroring the google connector) with tests
covering teardown-on-404, write-back swallow, and non-404 rethrow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the vitest@^2.1.8 entry for connectors/google-tasks so
`pnpm install --frozen-lockfile` passes in CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@KrisBraun
KrisBraun merged commit 9aced82 into mainJun 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KrisBraun