Skip to content

feat: Chief of Staff email pipeline with Slack approval flow#1992

Open
nickleeke wants to merge 26 commits intoelie222:mainfrom
nickleeke:feat/chief-of-staff-pipeline
Open

feat: Chief of Staff email pipeline with Slack approval flow#1992
nickleeke wants to merge 26 commits intoelie222:mainfrom
nickleeke:feat/chief-of-staff-pipeline

Conversation

@nickleeke
Copy link
Copy Markdown

Summary

  • Full email processing pipeline: Gmail Pub/Sub → pre-filter → Claude AI (Sonnet) → Slack approval → action execution
  • Slack Block Kit UI with approve/edit/reject buttons for Nick's mobile workflow
  • Acuity scheduling integration with 6-calendar availability checking and day protection rules
  • VIP detection, venture routing (Smart College/Praxis/Personal), voice/tone matching
  • Cron jobs for retry, batch summary, signature refresh, and VIP cache refresh
  • Database migration with 9 new tables and 6 enums
  • Fix: NODE_OPTIONS heap size for Docker runtime and build scripts

New Files (54 files, ~7000 lines)

  • apps/web/app/api/chief-of-staff/ — webhook, Slack interactions, cron routes
  • apps/web/utils/chief-of-staff/ — engine, tools, pre-filter, calendar, Acuity, Slack, VIP, shipping
  • apps/web/config/system-prompt.md — Claude system prompt content
  • apps/web/prisma/migrations/20260322... — database migration
  • go-live-checklist.html — interactive deployment checklist

Test plan

  • 227 test files pass (2444 tests, 0 failures)
  • Deploy via Dockerfile on Railway (standalone server)
  • Walk through go-live checklist (Pub/Sub, Slack app, Acuity, env vars)
  • Send test email and verify Slack notification appears
  • Test approve/edit/reject button interactions

🤖 Generated with Claude Code

nickleeke and others added 26 commits March 21, 2026 19:34
Architecture spec, skill source (SKILL.md + references), design specification,
and 21-task implementation plan for the Chief of Staff email processing pipeline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pipeline

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements a thin HTTP wrapper for the Acuity Scheduling REST API with
Basic Auth, exponential backoff on 429 rate limits (max 3 retries), and
typed error handling via AcuityApiError. Includes 6 Vitest tests covering
auth, error cases, retry behavior, and POST body serialization.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements prefix-parser (HARD_BLOCK/SOFT/INFORMATIONAL via ~, FYI:)
and day-protection (Tuesday always blocked, Friday blocked for non-VIPs)
with full vitest coverage (11 tests, all passing).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion

Queries all 6 Google Calendars for conflicts with 15-min buffers, applies
prefix conventions (~ soft, FYI: ignored), enforces day protection short-circuit,
treats Nutrition/Workout as always soft, and always hard-blocks RMS Work events.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Defines 7 tools using the AI SDK tool() function with Zod schemas:
check_calendar, check_acuity_availability, get_client_history,
book_appointment, reschedule_appointment, cancel_appointment,
and create_gmail_draft (with signature appended).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements processEmailWithClaude() using generateText with maxSteps:10
for multi-turn tool calling. Claude is instructed to return JSON in its
final message which is parsed into a typed CosEngineResponse. Includes
5 unit tests covering valid JSON parsing, call signature, email content
inclusion, invalid JSON error handling, and draft responses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements action handlers (handleApprove, handleEdit, handleEditSubmit,
handleReject) and the Next.js API route at
/api/chief-of-staff/slack/interactions that verifies Slack signatures,
routes block_actions and view_submission events, and delegates to the
appropriate Gmail + Prisma + Slack operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the Gmail Pub/Sub webhook endpoint and async pipeline
processor that ties together pre-filtering, Claude processing,
venture detection, and Slack posting for the Chief of Staff bot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verifies pre-filter, venture detection, day protection, prefix parser,
and shipping parser work together correctly using real inputs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… checklist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The cross-env NODE_OPTIONS=16GB was only applied to prisma migrate deploy
due to the && starting a new shell context, leaving next build with
default memory limits and causing OOM at ~480MB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The standalone Next.js server was OOMing at ~475MB on Railway because
no heap limit was configured for the runtime container. Defaults to
1GB but respects any NODE_OPTIONS set via Railway env vars.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 22, 2026

@nickleeke is attempting to deploy a commit to the Inbox Zero OSS Program Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 22, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

30 issues found across 61 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/web/utils/chief-of-staff/pre-filter.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/pre-filter.ts:46">
P2: extractDomain parses the raw From header with /@([^>]+)/, which can capture the wrong substring when the display name or comments contain '@' (valid RFC 5322 format). This can mis-parse senderDomain and break allowlist/blocklist routing. Consider extracting the address portion first and then taking the last '@' domain.</violation>

<violation number="2" location="apps/web/utils/chief-of-staff/pre-filter.ts:61">
P2: End-anchored shipping domain regexes are tested against the raw From header, so common formatted headers like "Name" <tracking@ups.com> end with ">" and won’t match /ups\.com$/i. This causes shipping senders to be missed unless the subject keywords happen to match.</violation>

<violation number="3" location="apps/web/utils/chief-of-staff/pre-filter.ts:79">
P2: Allow/block domain comparisons only lowercase configured values; they don’t trim whitespace or strip a leading '@', so common config formats like "@example.com" won’t match senderDomain and rules silently miss.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/system-prompt.test.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/system-prompt.test.ts:5">
P2: Mock target mismatch: implementation imports "node:fs", so mocking "fs" won't intercept reads and the test may hit real files.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/engine.test.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/engine.test.ts:107">
P2: Test claims to verify correct model/system prompt but only checks key existence, missing assertions for `model` and concrete `system` value.</violation>
</file>

<file name="apps/web/.env.example">

<violation number="1" location="apps/web/.env.example:243">
P2: Duplicate CRON_SECRET entry in .env.example can override the earlier value; the later blank value may disable/break cron auth when loaded.</violation>
</file>

<file name="skill-source/references/calendar-intelligence.md">

<violation number="1" location="skill-source/references/calendar-intelligence.md:127">
P2: The decision tree allows FYI/~ prefixes to override blocking, but the Special Rules section says RMS Work events are always hard blocks regardless of prefix. This contradiction could cause implementers to ignore RMS conflicts that should block scheduling.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/slack/blocks.test.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/slack/blocks.test.ts:199">
P2: The urgent-marker assertion is tautological because the subject already contains "URGENT", so the regex passes even if the builder stops adding an urgency marker. This weakens coverage for the urgency indicator.</violation>
</file>

<file name="apps/web/app/api/chief-of-staff/webhook/route.ts">

<violation number="1" location="apps/web/app/api/chief-of-staff/webhook/route.ts:9">
P2: Webhook token validation is unconditionally enforced, so empty/unset verification token mode cannot work and valid Pub/Sub pushes may be rejected.</violation>

<violation number="2" location="apps/web/app/api/chief-of-staff/webhook/route.ts:18">
P2: Malformed Pub/Sub payloads will throw during JSON parsing before the 200 response is returned, defeating the intended immediate acknowledgment and causing retry loops for bad payloads.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/acuity/client.test.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/acuity/client.test.ts:18">
P2: Fake timers are enabled without guaranteed cleanup; if the test fails before `vi.useRealTimers()`, fake timers leak into subsequent tests.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/signatures/fetcher.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/signatures/fetcher.ts:14">
P2: Empty-string signatures are treated as cache misses because the cache-hit check requires `signatureHtml` to be truthy, causing repeated Gmail fetches for accounts with no signature.</violation>
</file>

<file name="go-live-checklist.html">

<violation number="1" location="go-live-checklist.html:695">
P3: `loadState` parses localStorage JSON without error handling, so malformed persisted data can throw and abort checklist initialization (progress bar/section state never initializes).</violation>
</file>

<file name="apps/web/config/system-prompt.md">

<violation number="1" location="apps/web/config/system-prompt.md:213">
P1: Scheduling is allowed to proceed when calendar conflict checks fail, which can cause bookings/reschedules to execute using incomplete availability data.</violation>

<violation number="2" location="apps/web/config/system-prompt.md:214">
P2: Prompt instructs drafting English responses for non‑English emails, which conflicts with the policy to reply in the language of the latest thread message.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/jobs/retry-failed.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/jobs/retry-failed.ts:49">
P2: retryFailedEmails never retries processing; it only increments retryCount, so failed emails will reach dead_letter without any reprocessing attempt.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/vip/detector.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/vip/detector.ts:20">
P1: `checkVipStatus` uses raw email strings for unique lookups and dedupe without canonicalization, which can miss cache/group matches and misclassify VIP status.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/calendar/day-protection.test.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/calendar/day-protection.test.ts:11">
P2: Test dates are parsed in the local timezone, but isDayProtected evaluates in America/Chicago, so the expected weekday can shift in different environments.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/calendar/day-protection.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/calendar/day-protection.ts:13">
P2: Intl.DateTimeFormat().format throws on invalid Date; isDayProtected doesn’t guard, so malformed startTime/endTime strings can crash scheduling instead of returning a safe result.</violation>
</file>

<file name="skill-source/SKILL.md">

<violation number="1" location="skill-source/SKILL.md:197">
P2: Prompt instructions conflict on sending vs drafting scheduling confirmations, creating ambiguous and potentially unsafe assistant behavior.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/calendar/checker.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/calendar/checker.ts:81">
P1: Events with no summary are skipped, which can hide real time conflicts and incorrectly mark slots as available.</violation>
</file>

<file name="chief-of-staff-bot-architecture.md">

<violation number="1" location="chief-of-staff-bot-architecture.md:249">
P2: Gmail Pub/Sub setup URL points to `/webhook/gmail`, but the actual webhook handler is `/api/chief-of-staff/webhook`, so following this doc will send Pub/Sub to a non-existent endpoint.</violation>

<violation number="2" location="chief-of-staff-bot-architecture.md:256">
P2: Slack interactivity URL points to `/slack/events`, but the actual handler is `/api/chief-of-staff/slack/interactions`, so Slack interactions will fail if configured per the doc.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/acuity/client.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/acuity/client.ts:61">
P2: Network-level fetch failures are not retried or wrapped in AcuityApiError; any thrown error will exit the loop immediately, bypassing the retry/structured error handling logic.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/tools.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/tools.ts:56">
P2: startTime/endTime strings are parsed without validation; Invalid Date inputs will throw in downstream availability checks (toISOString/Intl.DateTimeFormat).</violation>

<violation number="2" location="apps/web/utils/chief-of-staff/tools.ts:221">
P1: Unsanitized header interpolation allows CRLF header injection when constructing raw Gmail MIME drafts.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/system-prompt.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/system-prompt.ts:16">
P1: System prompt loading relies on `process.cwd()` and an unguarded sync read, which can throw `ENOENT` in deploy/runtime contexts where CWD differs from `apps/web`.</violation>
</file>

<file name="apps/web/utils/chief-of-staff/slack/actions.ts">

<violation number="1" location="apps/web/utils/chief-of-staff/slack/actions.ts:51">
P2: Slack action handlers lack state/idempotency guards, so repeated or conflicting actions can send/delete drafts multiple times and overwrite terminal statuses.</violation>
</file>

<file name="apps/web/app/api/chief-of-staff/webhook/process.ts">

<violation number="1" location="apps/web/app/api/chief-of-staff/webhook/process.ts:213">
P2: lastSyncedHistoryId advances even when message processing fails, so failed Gmail history entries can be skipped permanently without retry.</violation>

<violation number="2" location="apps/web/app/api/chief-of-staff/webhook/process.ts:340">
P2: shippingEvent.calendarEventId is hardcoded to "created" instead of storing the provider’s actual event ID from the insert response, which prevents reliable updates/deletes or reconciliation.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

| Gmail search returns no results | Report "Inbox clear — nothing new since last check" |
| Can't determine email category | Default to **Client/Parent** if from a person, **Notification** if automated |
| Acuity is unreachable | Draft response saying "Let me check my availability and get back to you shortly" |
| Calendar check fails | Note the gap and proceed with Acuity-only data, flagging the limitation |
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Scheduling is allowed to proceed when calendar conflict checks fail, which can cause bookings/reschedules to execute using incomplete availability data.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/config/system-prompt.md, line 213:

<comment>Scheduling is allowed to proceed when calendar conflict checks fail, which can cause bookings/reschedules to execute using incomplete availability data.</comment>

<file context>
@@ -0,0 +1,227 @@
+| Gmail search returns no results | Report "Inbox clear — nothing new since last check" |
+| Can't determine email category | Default to **Client/Parent** if from a person, **Notification** if automated |
+| Acuity is unreachable | Draft response saying "Let me check my availability and get back to you shortly" |
+| Calendar check fails | Note the gap and proceed with Acuity-only data, flagging the limitation |
+| Email is in a language other than English | Note the language and attempt categorization; draft response in English with a note to Nick |
+| Email thread (not just single message) | Read the full thread for context before categorizing/drafting |
</file context>
Fix with Cubic

prisma: PrismaClient,
): Promise<VipResult> {
// 1. Check cache
const cached = await prisma.vipCache.findUnique({ where: { clientEmail } });
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: checkVipStatus uses raw email strings for unique lookups and dedupe without canonicalization, which can miss cache/group matches and misclassify VIP status.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/chief-of-staff/vip/detector.ts, line 20:

<comment>`checkVipStatus` uses raw email strings for unique lookups and dedupe without canonicalization, which can miss cache/group matches and misclassify VIP status.</comment>

<file context>
@@ -0,0 +1,82 @@
+  prisma: PrismaClient,
+): Promise<VipResult> {
+  // 1. Check cache
+  const cached = await prisma.vipCache.findUnique({ where: { clientEmail } });
+  if (cached && Date.now() - cached.lastChecked.getTime() < CACHE_TTL_MS) {
+    let groupName: string | null = null;
</file context>
Fix with Cubic

const events = result.value.data.items ?? [];

for (const event of events) {
if (!event.summary) continue;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Events with no summary are skipped, which can hide real time conflicts and incorrectly mark slots as available.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/chief-of-staff/calendar/checker.ts, line 81:

<comment>Events with no summary are skipped, which can hide real time conflicts and incorrectly mark slots as available.</comment>

<file context>
@@ -0,0 +1,117 @@
+    const events = result.value.data.items ?? [];
+
+    for (const event of events) {
+      if (!event.summary) continue;
+      const parsed = parseEventPrefix(event.summary);
+      const eventStart = event.start?.dateTime ?? event.start?.date ?? "";
</file context>
Fix with Cubic

// Build raw MIME message
const headers: string[] = [
`To: ${to}`,
`Subject: ${subject}`,
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Unsanitized header interpolation allows CRLF header injection when constructing raw Gmail MIME drafts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/chief-of-staff/tools.ts, line 221:

<comment>Unsanitized header interpolation allows CRLF header injection when constructing raw Gmail MIME drafts.</comment>

<file context>
@@ -0,0 +1,267 @@
+      // Build raw MIME message
+      const headers: string[] = [
+        `To: ${to}`,
+        `Subject: ${subject}`,
+        `From: ${emailAddress}`,
+        "MIME-Version: 1.0",
</file context>
Fix with Cubic


function getBasePrompt(): string {
if (basePromptCache) return basePromptCache;
const promptPath = path.join(process.cwd(), "config", "system-prompt.md");
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: System prompt loading relies on process.cwd() and an unguarded sync read, which can throw ENOENT in deploy/runtime contexts where CWD differs from apps/web.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/chief-of-staff/system-prompt.ts, line 16:

<comment>System prompt loading relies on `process.cwd()` and an unguarded sync read, which can throw `ENOENT` in deploy/runtime contexts where CWD differs from `apps/web`.</comment>

<file context>
@@ -0,0 +1,68 @@
+
+function getBasePrompt(): string {
+  if (basePromptCache) return basePromptCache;
+  const promptPath = path.join(process.cwd(), "config", "system-prompt.md");
+  basePromptCache = fs.readFileSync(promptPath, "utf-8");
+  return basePromptCache;
</file context>
Fix with Cubic

execute: async ({ startTime, endTime, isVip }) => {
return checkCalendarAvailability({
calendarClient: calendarAuth,
startTime: new Date(startTime),
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: startTime/endTime strings are parsed without validation; Invalid Date inputs will throw in downstream availability checks (toISOString/Intl.DateTimeFormat).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/chief-of-staff/tools.ts, line 56:

<comment>startTime/endTime strings are parsed without validation; Invalid Date inputs will throw in downstream availability checks (toISOString/Intl.DateTimeFormat).</comment>

<file context>
@@ -0,0 +1,267 @@
+    execute: async ({ startTime, endTime, isVip }) => {
+      return checkCalendarAvailability({
+        calendarClient: calendarAuth,
+        startTime: new Date(startTime),
+        endTime: new Date(endTime),
+        isVip,
</file context>
Fix with Cubic

@@ -0,0 +1,338 @@
import { WebClient } from "@slack/web-api";
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Slack action handlers lack state/idempotency guards, so repeated or conflicting actions can send/delete drafts multiple times and overwrite terminal statuses.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/chief-of-staff/slack/actions.ts, line 51:

<comment>Slack action handlers lack state/idempotency guards, so repeated or conflicting actions can send/delete drafts multiple times and overwrite terminal statuses.</comment>

<file context>
@@ -0,0 +1,338 @@
+  } = params;
+
+  // 1. Look up CosPendingDraft by slackMessageTs
+  const draft = await prisma.cosPendingDraft.findUnique({
+    where: { slackMessageTs },
+  });
</file context>
Fix with Cubic

}

// Update lastSyncedHistoryId
const lastEntry = historyEntries[historyEntries.length - 1];
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: lastSyncedHistoryId advances even when message processing fails, so failed Gmail history entries can be skipped permanently without retry.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/api/chief-of-staff/webhook/process.ts, line 213:

<comment>lastSyncedHistoryId advances even when message processing fails, so failed Gmail history entries can be skipped permanently without retry.</comment>

<file context>
@@ -0,0 +1,561 @@
+    }
+
+    // Update lastSyncedHistoryId
+    const lastEntry = historyEntries[historyEntries.length - 1];
+    if (lastEntry?.id) {
+      await prisma.$executeRaw`
</file context>
Fix with Cubic

data: {
messageId,
emailAccountId: emailAccount.id,
calendarEventId: "created",
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: shippingEvent.calendarEventId is hardcoded to "created" instead of storing the provider’s actual event ID from the insert response, which prevents reliable updates/deletes or reconciliation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/api/chief-of-staff/webhook/process.ts, line 340:

<comment>shippingEvent.calendarEventId is hardcoded to "created" instead of storing the provider’s actual event ID from the insert response, which prevents reliable updates/deletes or reconciliation.</comment>

<file context>
@@ -0,0 +1,561 @@
+          data: {
+            messageId,
+            emailAccountId: emailAccount.id,
+            calendarEventId: "created",
+            itemDescription,
+          },
</file context>
Fix with Cubic

function loadState() {
const saved = localStorage.getItem(STORAGE_KEY);
if (!saved) return;
const state = JSON.parse(saved);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: loadState parses localStorage JSON without error handling, so malformed persisted data can throw and abort checklist initialization (progress bar/section state never initializes).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At go-live-checklist.html, line 695:

<comment>`loadState` parses localStorage JSON without error handling, so malformed persisted data can throw and abort checklist initialization (progress bar/section state never initializes).</comment>

<file context>
@@ -0,0 +1,750 @@
+  function loadState() {
+    const saved = localStorage.getItem(STORAGE_KEY);
+    if (!saved) return;
+    const state = JSON.parse(saved);
+    const checkboxes = document.querySelectorAll('.task input[type="checkbox"]');
+    checkboxes.forEach((cb, i) => {
</file context>
Fix with Cubic

Sign up for free to 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.

2 participants