Skip to content

fix(keys): ephemeral key TTL 15m → 60m (long runs outlive the key, chat#1839)#751

Merged
sweetmantech merged 2 commits into
mainfrom
fix/ephemeral-key-ttl-60m
Jul 3, 2026
Merged

fix(keys): ephemeral key TTL 15m → 60m (long runs outlive the key, chat#1839)#751
sweetmantech merged 2 commits into
mainfrom
fix/ephemeral-key-ttl-60m

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Why

Headless runs that outlive the 15-minute ephemeral key TTL lose auth mid-flight — typically at the final email send, the most expensive place to fail. On 2026-07-03 a customer-facing LA EQUIS report run hit a runAgentStep timeout, retried 3× (each retry restarts the whole agent loop, ~10+ min per attempt), and every POST /api/emails attempt then 401'd on the expired key: three rejected rows in email_send_log with null account_id, zero deliveries. This is the exact failure signature documented in recoupable/chat#1839.

What

  • DEFAULT_EPHEMERAL_KEY_TTL_MS: 15 min → 60 min, with a JSDoc explaining the reasoning.
  • Test asserting the 60-minute default (written first, confirmed RED, then GREEN).

Why this is safe

The TTL is only the backstop: runAgentWorkflow revokes the key the moment the run ends (deleteEphemeralKeyStep in the finally block), so this change does not extend key life on normal completion — it only stops killing legitimate long runs. The chat#1839 decision to prefer refresh-over-extend still stands as the proper long-term fix; that issue also names TTL scaling as the acceptable fallback, which this implements in its simplest form.

Verification

  • pnpm test lib/keys/ — 13/13 green.
  • pnpm exec tsc --noEmit — the only errors are pre-existing on main in lib/trigger/__tests__ (untouched by this diff).

Refs recoupable/chat#1839.

🤖 Generated with Claude Code


Summary by cubic

Increase ephemeral account key TTL from 15 to 60 minutes so long headless runs stay authenticated through retries. Aligns with recoupable/chat#1839; TTL remains a backstop since keys are revoked when runs end.

  • Bug Fixes
    • Set DEFAULT_EPHEMERAL_KEY_TTL_MS to 60m; expanded doc comment with retry rationale.
    • Added a TTL default test; updated mint test to use the default expiry.

Written for commit 324367b. Summary will update on new commits.

Review in cubic

…hat#1839)

The TTL is only the backstop: runAgentWorkflow revokes the key on run end
via deleteEphemeralKeyStep, so this does not extend key life on normal
completion. But a runAgentStep timeout retries the whole agent loop
(~10+ min per attempt), and on 2026-07-03 a customer run's final sends
all 401'd on the expired key after 3 step retries (email_send_log
'rejected' rows with null account_id). 60 minutes covers several retry
cycles; the chat#1839 refresh mechanism remains the proper long-term fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Ready Ready Preview Jul 3, 2026 7:29pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@sweetmantech, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e4781e7c-ba44-4fac-81d3-f13bbf84d103

📥 Commits

Reviewing files that changed from the base of the PR and between 465aae4 and 324367b.

⛔ Files ignored due to path filters (1)
  • lib/keys/__tests__/mintEphemeralAccountKey.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (1)
  • lib/keys/mintEphemeralAccountKey.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ephemeral-key-ttl-60m

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 2 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant Runner as runAgentWorkflow
    participant KeyModule as mintEphemeralAccountKey
    participant DB as Supabase (api_keys table)
    participant Agent as runAgentStep (loop)
    participant Email as POST /api/emails

    Note over Runner,Email: Ephemeral Key Lifecycle (long-run scenario)

    Runner->>KeyModule: mintEphemeralAccountKey()
    KeyModule->>KeyModule: TTL = 60 * 60 * 1000 (60m)
    KeyModule->>DB: insertApiKey(expires_at = now + 60m)
    DB-->>KeyModule: keyId, rawKey
    KeyModule-->>Runner: rawKey + keyId

    loop Agent execution with retries
        Runner->>Agent: runAgentStep()
        alt Step timeout (10+ min)
            Agent-->>Runner: timeout / retry
            Runner->>Agent: runAgentStep() retry
        else Step succeeds
            Agent-->>Runner: step result
        end
    end

    Runner->>Email: POST /api/emails (with ephemeral key)
    Note over Runner,Email: Key valid for 60m — survives retries
    Email->>DB: verify key validity (expires_at check)
    DB-->>Email: valid
    Email-->>Runner: 200 OK

    opt Run completes normally
        Runner->>Runner: deleteEphemeralKeyStep (finally block)
        Runner->>DB: revoke key (cleanup)
        Note over Runner,DB: Key revoked regardless of TTL
    end

    alt Key expired before run ends (was 15m TTL)
        Note over Email: NEW: 60m TTL prevents this failure mode
        Email->>DB: verify key validity
        DB-->>Email: expired (401)
        Email-->>Runner: 401 Unauthorized
        Note over Runner,Email: History: 3 rejected rows in email_send_log, null account_id, zero deliveries
    end
Loading

Requires human review: Extends key TTL from 15 to 60 minutes, a security-sensitive change affecting authentication lifetime. Requires human review to assess timing and revocation guarantees.

Re-trigger cubic

@sweetmantech sweetmantech requested a deployment to production July 3, 2026 19:08 Abandoned
@sweetmantech

Copy link
Copy Markdown
Contributor Author

Preview verification — 2026-07-03 ~19:15 UTC

Tested against the PR's preview deployment api-ekjm9lhrz-recoup.vercel.app (branch fix/ephemeral-key-ttl-60m), which shares the prod database.

1. Minted TTL is 60 minutes ✅

Fired POST /api/chat/runs on the preview (19:12:29 UTC, run wrun_01KWMPBJEWPEE77YWCQ0YTJ5HW) and read the minted row from account_api_keys:

name created_at expires_at TTL
ephemeral:chat-generate 2026-07-03 19:12:44 2026-07-03 20:12:44 60 min

2. Prod control still mints 15 minutes ✅

Production (main) behavior for contrast: today's 18:18 UTC customer run minted a 15-min key that expired mid-run — its three POST /api/emails attempts at 18:39–18:40 all 401'd and were logged as rejected rows with null account_id in email_send_log (the chat#1839 signature this PR addresses). A prod control run's key was minted and already revoked within seconds of completion, consistent with the 15-min code on main.

3. TTL remains a backstop — revocation on run end still works ✅

The preview run's 60-min key was deleted when its run ended (~3 minutes after mint; row gone from account_api_keys by 19:16 UTC). So the extended TTL does not lengthen key life on normal completion — it only protects long runs (step retries) from mid-flight expiry.

Also exercised end-to-end on the preview: a first trivial run (19:10:57, wrun_01KWMP8XH6XABG01XGG3VE03MT) completed with its key minted and revoked so quickly it couldn't be sampled — the happy path is unchanged.

🤖 Generated with Claude Code

@sweetmantech sweetmantech merged commit da9e226 into main Jul 3, 2026
4 of 6 checks passed
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.

1 participant