feat: S3 presigned URLs for private media delivery#2887
Conversation
🦋 Changeset detectedLatest commit: a1e283f The changes in this PR will be included in the next version bump. This PR includes changesets to release 10 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
TL;DR — When S3 is the blob storage backend, conversation media URLs are now resolved as presigned S3 Key changes
Summary | 18 files | 5 commits | base: S3 presigned URL generationAdded
s3-provider.ts · types.ts · s3-provider.test.ts Async blob URI resolution with graceful fallback
Both conversation route handlers (
resolve-blob-uris.ts · resolve-blob-uris.test.ts · run/conversations.ts · manage/conversations.ts S3 blob storage deployment guideNew documentation page at
|
|
TL;DR — When S3 is the blob storage backend, conversation media URLs are now resolved as presigned S3 Key changes
Summary | 21 files | 13 commits | base: S3 presigned URL generation
Added
Async blob URI resolution with graceful fallback
Configurable presigned URL expiry
The env var is added to both
S3 blob storage deployment guideNew documentation page at
Design spec and evidenceThe
|
There was a problem hiding this comment.
PR Review Summary
(0) Total Issues | Risk: Low
This is a well-implemented feature that adds S3 presigned URL support for private media delivery. The implementation follows the spec correctly, has good error handling with graceful fallback, and includes comprehensive test coverage.
🔴❗ Critical (0) ❗🔴
None.
🟠⚠️ Major (0) 🟠⚠️
None.
🟡 Minor (0) 🟡
None.
💭 Consider (4) 💭
Inline Comments:
- 💭 Consider:
s3-blob-storage.mdx:69S3-compatible services guidance incomplete
💭 1) resolve-blob-uris.test.ts:90 Fallback test logging verification
Issue: The fallback test verifies the proxy URL is returned when presigned URL generation fails, but does not verify the warning log is emitted.
Why: The logger.warn call is a debugging aid that could silently break without detection.
Fix: Optionally add a spy on the logger to verify logger.warn is called with the expected key and error context.
💭 2) resolve-blob-uris.test.ts:240 List-level presigned URL test
Issue: The resolveMessagesListBlobUris test only exercises the proxy URL fallback path.
Why: While the function delegates to resolveMessageBlobUris (indirectly covered), explicit list-level presigned URL coverage would catch any Promise.all handling bugs.
Fix: Consider adding a test that configures mockGetPresignedUrl and verifies presigned URLs work for multiple messages in a list.
💭 3) s3-blob-storage.mdx:20-26 AWS CLI prerequisite note
Issue: Step 1's aws s3 mb command assumes AWS CLI is installed.
Why: Users without AWS CLI will hit an error. While the target audience likely has it, write-docs standards recommend stating prerequisites.
Fix: Consider adding a brief note: "Requires AWS CLI configured with credentials that have s3:CreateBucket permission."
💡 APPROVE WITH SUGGESTIONS
Summary: Solid implementation. The code correctly makes resolveMessageBlobUris() async, implements presigned URL generation in the S3 provider, and gracefully falls back to proxy URLs on failure. Error handling is appropriate with warning logs for debugging. Test coverage is comprehensive covering happy path, fallback, mixed content, and error scenarios. The spec is thorough and the security improvements (domain isolation for cookie/XSS protection) are well-reasoned. The Consider items above are optional polish — this PR is ready to merge.
Discarded (0)
No findings discarded.
Reviewers (5)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-standards |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-errors |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-appsec |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-tests |
3 | 0 | 2 | 0 | 0 | 0 | 1 |
pr-review-docs |
4 | 0 | 2 | 0 | 1 | 0 | 1 |
| Total | 7 | 0 | 4 | 0 | 1 | 0 | 2 |
Note: 2 INFO-level findings from docs/tests reviewers classified as Consider; 2 very low-confidence items discarded (dotenv syntax tag, Steps component consistency — both are style preferences matching existing patterns in the codebase).
| ```bash | ||
| BLOB_STORAGE_S3_ENDPOINT=https://your-custom-endpoint | ||
| BLOB_STORAGE_S3_FORCE_PATH_STYLE=true | ||
| ``` |
There was a problem hiding this comment.
💭 Consider: S3-compatible services guidance incomplete
Issue: The section lists Cloudflare R2, DigitalOcean Spaces, and Backblaze B2 but doesn't clarify which services require BLOB_STORAGE_S3_FORCE_PATH_STYLE=true vs which do not.
Why: Users may incorrectly set (or omit) this flag, causing presigned URL generation to fail with confusing errors. R2 supports virtual-hosted style and doesn't need path-style; MinIO and B2 typically do.
Fix: Consider adding clarification like:
- Cloudflare R2: Set
BLOB_STORAGE_S3_ENDPOINTonly (R2 uses virtual-hosted style) - MinIO, Backblaze B2: Set both
BLOB_STORAGE_S3_ENDPOINTandBLOB_STORAGE_S3_FORCE_PATH_STYLE=true
There was a problem hiding this comment.
Medium urgency — clean implementation with good fallback semantics, but one security gap worth addressing before merge and one pre-existing concern to track.
Summary: The core change — making resolveMessageBlobUris async and adding presigned URL generation via @aws-sdk/s3-request-presigner — is well-structured. The fallback from presigned URL → proxy URL on error is a solid resilience pattern. All call sites are correctly awaited, tests cover the happy path, fallback, and mixed-content scenarios, and the lockfile changes are minimal and correct.
Pre-existing concern (not blocking): Streaming/SSE response paths (chat.ts, chatDataStream.ts, executions.ts) do not resolve blob:// URIs. If streamed responses ever include blob:// references in message content parts, they would reach clients unresolved. This is pre-existing behavior and not introduced by this PR, but worth tracking as a follow-up given that presigned URLs make the non-streaming path more capable.
Claude Opus | 𝕏
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| it('resolves blob file parts to media proxy URLs when presigned URLs are not available', async () => { |
There was a problem hiding this comment.
The first test ("resolves blob file parts to media proxy URLs when presigned URLs are not available") relies on the top-level mock returning { getPresignedUrl: undefined }, while all other tests re-mock getBlobStorageProvider within the test body. This works because beforeEach calls vi.clearAllMocks() but does not reset the module-level mock's return value — so the top-level undefined presigned URL mock is still in effect.
This is fragile: if someone adds a test above that changes the mock return value and forgets to reset it, this test breaks silently. Consider explicitly setting getBlobStorageProvider in this test too, matching the pattern of all other tests in the suite.
Ito Test Report ✅17 test cases ran. 17 passed. All 17 test cases passed, confirming end-to-end conversation media handling works as intended across APIs, UI, edge cases, and documentation: blob:// file parts are correctly rewritten to presigned S3 URLs in S3 mode (including Vercel run format), non-blob/external content is preserved, malformed blob keys are safely filtered without breaking responses, and behavior remains deterministic under concurrent and rapid user interactions. The most important risk checks also passed, showing resilient and secure fallbacks and access controls—proxy URLs are used when S3 is disabled or signing fails (including selective per-part failure), mobile/deep-link navigation continues to render resolved attachments, docs are discoverable with correct setup guidance, traversal/null-byte/backslash mediaKey attacks are rejected with 400, legacy API keys are limited to allowed endpoints, tampered signatures are denied, and stale URL replay is blocked while fresh URLs continue to work. ✅ Passed (17)Commit: Tell us how we did: Give Ito Feedback |
|
Error agent completed without reporting progress
|
|
Acknowledged the author's feedback on the defense-in-depth suggestion for
|
|
Addressed review feedback — made presigned URL expiry configurable via Changes (
|
| - **Domain isolation** — media is served from `*.s3.amazonaws.com`, separate from your API domain | ||
| - **Time-limited access** — presigned URLs expire after 1 hour | ||
|
|
||
| When S3 is not configured, the framework falls back to the local filesystem with a server-side media proxy. |
There was a problem hiding this comment.
I think these docs are not entirely correct. There is an option to set the Vercel Blob storage as a provider as well which can be used on production, but the files will be returned via the media proxy rather than s3 presigned urls.
So order is use s3 if it is available, then use vercel blob if it is available then use local storage. Both local storage and vercel blob serve through the media proxy
Review comments addressed (a1255e2)@amikofalvy's comments:
Pullfrog's comments:
Claude bot's comment:
CI failure: |
There was a problem hiding this comment.
PR Review Summary
(0) Total Issues | Risk: Low
Delta Review Scope
This is a re-review scoped to 2 commits since the last automated review (33883b1):
6b871fc32— Make presigned URL expiry configurable viaBLOB_STORAGE_PRESIGNED_URL_EXPIRY_SECONDSa1255e2fa— Address PR review comments
🔴❗ Critical (0) ❗🔴
None.
🟠⚠️ Major (0) 🟠⚠️
None.
🟡 Minor (0) 🟡
None.
💭 Consider (0) 💭
None.
Delta Changes Assessment
All delta changes correctly address prior review feedback:
| Change | Status | Notes |
|---|---|---|
env.ts — Zod schema for BLOB_STORAGE_PRESIGNED_URL_EXPIRY_SECONDS |
✅ | Proper validation (int, min 60, max 604800, default 7200) with description |
s3-provider.ts — Read expiry from env |
✅ | Removed hardcoded constant, uses env.BLOB_STORAGE_PRESIGNED_URL_EXPIRY_SECONDS |
s3-provider.test.ts — Updated test expectations |
✅ | Default expects 7200, custom expiry test uses 900, all mocks include env var |
resolve-blob-uris.test.ts — Explicit mock setup |
✅ | Addresses fragile test setup noted in prior review |
s3-blob-storage.mdx — Docs updates |
✅ | Added Vercel Blob fallback mention, presigned URL expiry config section, S3-compatible services table |
.env.example files |
✅ | Consistent with env.ts schema |
✅ APPROVE
Summary: The delta changes cleanly address all prior review feedback. The configurable presigned URL expiry is properly validated via Zod with sensible bounds (1 minute to 7 days), tests are updated to cover the new behavior, and documentation now accurately describes the storage backend priority including Vercel Blob fallback. No new issues introduced. Ready to merge! 🎉
Discarded (0)
No findings discarded.
Reviewers (1)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
orchestrator (delta) |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Total | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Note: Delta-scoped re-review — all changes address prior feedback correctly.
a1255e2 to
c8b56c4
Compare
There was a problem hiding this comment.
PR Review Summary
(0) Total Issues | Risk: Low
Review Scope
Third automated review following c8b56c482 — all prior feedback has been addressed. Dispatched 5 domain-specific reviewers:
pr-review-standards— code quality, async patterns, type safetypr-review-errors— error handling, fallback patternspr-review-appsec— presigned URL security, S3 accesspr-review-tests— test coverage qualitypr-review-docs— documentation accuracy
🔴❗ Critical (0) ❗🔴
None.
🟠⚠️ Major (0) 🟠⚠️
None.
🟡 Minor (0) 🟡
None.
💭 Consider (0) 💭
None — all Consider items from prior reviews have been addressed or acknowledged.
🕐 Pending Recommendations (2)
- 💭
resolve-blob-uris.test.ts:240List-level presigned URL test coverage (from prior claude review) - 💭
s3-blob-storage.mdx:20-26AWS CLI prerequisite note (from prior claude review)
✅ APPROVE
Summary: Clean implementation of S3 presigned URL support for private media delivery. The code correctly makes resolveMessageBlobUris() async, implements presigned URL generation with proper error handling and graceful fallback to proxy URLs, and includes comprehensive test coverage. All prior review feedback has been addressed — the configurable presigned URL expiry is properly validated via Zod (60s to 7 days, default 2 hours), documentation accurately describes the storage backend priority (S3 → Vercel Blob → local), and the S3-compatible services table clarifies path-style requirements. Ready to ship! 🚀
Discarded (4)
| Location | Issue | Reason Discarded |
|---|---|---|
resolve-blob-uris.test.ts:97 |
Missing test for malformed key + presigned URL failure combo | Edge case where two error paths combine; individual paths are tested. Low risk. |
resolve-blob-uris.test.ts:247 |
List-level presigned URL test | Duplicate of prior claude review Consider item |
s3-provider.test.ts:112 |
GetObjectCommand parameters not verified | Low criticality; expiry is verified which is the key behavioral contract |
s3-provider.test.ts:161 |
Error cause not preserved | Nice-to-have; error message contains sufficient context |
Reviewers (5)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-standards |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-errors |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-appsec |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-tests |
4 | 0 | 0 | 0 | 0 | 1 | 4 |
pr-review-docs |
1 | 0 | 0 | 0 | 0 | 1 | 0 |
| Total | 5 | 0 | 0 | 0 | 0 | 2 | 4 |
Note: Test reviewer findings were assessed as low-priority edge cases or duplicates of prior feedback. Docs reviewer returned INFO-only confirmation that documentation is accurate.
- Add optional getPresignedUrl() to BlobStorageProvider interface - Implement in S3BlobStorageProvider using @aws-sdk/s3-request-presigner - Make resolveMessageBlobUris() async with presigned URL first, manage proxy fallback for non-S3 backends (Option D hybrid) - Update both call sites (run + manage conversation routes) with await - Add presigned URL tests to s3-provider and resolve-blob-uris test suites - Include full spec with evidence files Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Covers S3 setup, IAM permissions, env vars, S3-compatible services, storage backend priority, and presigned URL delivery flow. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Wrap getPresignedUrl() in try-catch so failures fall through to manage proxy URL instead of crashing the entire conversation response - Add test for presigned URL failure → proxy fallback path - Add mixed-content test with presigned URLs active - Fix doc icon quoting convention Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
… docs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…NED_URL_EXPIRY_SECONDS` - Add `BLOB_STORAGE_PRESIGNED_URL_EXPIRY_SECONDS` to env.ts Zod schema (default 7200s / 2 hours, range 60–604800) - Replace hardcoded `DEFAULT_PRESIGNED_EXPIRY_SECONDS` constant in s3-provider.ts with env var lookup - Update tests to use env var in mocks and verify new default - Add env var to .env.example files and deployment docs
- Clarify Vercel Blob is also a valid production backend (serves via proxy) - Add per-service S3-compatible path-style guidance (R2 vs B2 vs Spaces) - Make first resolve-blob-uris test explicitly set its mock (test isolation) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…drift Pin @aws-sdk/s3-request-presigner to 3.995.0 (same as resolved client-s3) to minimize pnpm-lock.yaml changes and prevent react version mismatch that caused agents-email test failures in CI. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…ssage Addresses PR feedback: call getBlobStorageProvider() once in resolveMessagesListBlobUris and pass the provider through to resolveMessageBlobUris, avoiding N singleton lookups per conversation retrieval. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Reset pnpm-lock.yaml from main per repo guidelines to prevent resolution drift that was causing monaco-editor ESM import failures in agents-manage-ui tests. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…nd AWS CLI prerequisite - Add test for resolveMessagesListBlobUris with presigned URLs active across multiple messages, covering Promise.all handling - Add AWS CLI prerequisite note to S3 setup docs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
a0c07c6 to
a1e283f
Compare
There was a problem hiding this comment.
PR Review Summary
(0) Total Issues | Risk: Low
Review Scope
Tenth automated review — no new commits since the previous review (a1e283f75). This PR has been thoroughly reviewed across 9 prior automated passes plus 1 human approval.
🔴❗ Critical (0) ❗🔴
None.
🟠⚠️ Major (0) 🟠⚠️
None.
🟡 Minor (0) 🟡
None.
💭 Consider (0) 💭
All Consider items from prior reviews have been addressed.
🕐 Pending Recommendations (0)
All prior Pending Recommendations have been addressed.
✅ APPROVE
Summary: This PR implements S3 presigned URL support for private media delivery — a well-designed feature with proper error handling and graceful fallback to proxy URLs. All review feedback has been addressed across 9 prior automated reviews:
| ✅ Addressed | Change |
|---|---|
| Provider lookup optimization | getBlobStorageProvider() called once per message list |
| Configurable presigned URL expiry | BLOB_STORAGE_PRESIGNED_URL_EXPIRY_SECONDS env var (60s–7 days, default 2hr) |
| Documentation accuracy | Storage backend priority clarified (S3 → Vercel Blob → local) |
| S3-compatible services guidance | Added table clarifying path-style requirements per provider |
| Test coverage | List-level presigned URL test added |
| Test fragility | Explicit mock setup in first test |
Implementation highlights:
resolveMessageBlobUris()now async withPromise.allfor concurrent resolution- S3 presigned URLs via
@aws-sdk/s3-request-presignerwith graceful fallback to manage proxy - Comprehensive test coverage (16 blob storage tests)
- Clear documentation with S3-compatible services guidance
With 1 human approval from mike-inkeep and no new changes since the last review, this PR is ready to merge. Ship it! 🚀
Discarded (0)
No findings discarded.
Reviewers (0)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
| Total | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Note: No sub-reviewers dispatched — no new changes to review since prior pass.
Preview URLsUse these stable preview aliases for testing this PR:
These point to the same Vercel preview deployment as the bot comment, but they stay stable and easier to find. Raw Vercel deployment URLs
|
|
🔎💬 Inkeep AI search and chat service is syncing content for source 'Inkeep Agent Framework Docs' |
* feat: add S3 presigned URL support for private media delivery - Add optional getPresignedUrl() to BlobStorageProvider interface - Implement in S3BlobStorageProvider using @aws-sdk/s3-request-presigner - Make resolveMessageBlobUris() async with presigned URL first, manage proxy fallback for non-S3 backends (Option D hybrid) - Update both call sites (run + manage conversation routes) with await - Add presigned URL tests to s3-provider and resolve-blob-uris test suites - Include full spec with evidence files Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * docs: add S3 blob storage deployment guide Covers S3 setup, IAM permissions, env vars, S3-compatible services, storage backend priority, and presigned URL delivery flow. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * chore: add changeset for S3 presigned URL support Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: add error handling for presigned URL failures with proxy fallback - Wrap getPresignedUrl() in try-catch so failures fall through to manage proxy URL instead of crashing the entire conversation response - Add test for presigned URL failure → proxy fallback path - Add mixed-content test with presigned URLs active - Fix doc icon quoting convention Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: correct callout type and remove inaccurate configurable claim in docs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * feat: make presigned URL expiry configurable via `BLOB_STORAGE_PRESIGNED_URL_EXPIRY_SECONDS` - Add `BLOB_STORAGE_PRESIGNED_URL_EXPIRY_SECONDS` to env.ts Zod schema (default 7200s / 2 hours, range 60–604800) - Replace hardcoded `DEFAULT_PRESIGNED_EXPIRY_SECONDS` constant in s3-provider.ts with env var lookup - Update tests to use env var in mocks and verify new default - Add env var to .env.example files and deployment docs * fix: address PR review comments - Clarify Vercel Blob is also a valid production backend (serves via proxy) - Add per-service S3-compatible path-style guidance (R2 vs B2 vs Spaces) - Make first resolve-blob-uris test explicitly set its mock (test isolation) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * chore: reset lockfile from main to minimize resolution drift Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: pin s3-request-presigner to match client-s3 to prevent lockfile drift Pin @aws-sdk/s3-request-presigner to 3.995.0 (same as resolved client-s3) to minimize pnpm-lock.yaml changes and prevent react version mismatch that caused agents-email test failures in CI. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * Resolve blob storage provider once per message list instead of per message Addresses PR feedback: call getBlobStorageProvider() once in resolveMessagesListBlobUris and pass the provider through to resolveMessageBlobUris, avoiding N singleton lookups per conversation retrieval. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * chore: reset lockfile from main and reinstall to fix CI Reset pnpm-lock.yaml from main per repo guidelines to prevent resolution drift that was causing monaco-editor ESM import failures in agents-manage-ui tests. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * Address remaining review suggestions: list-level presigned URL test and AWS CLI prerequisite - Add test for resolveMessagesListBlobUris with presigned URLs active across multiple messages, covering Promise.all handling - Add AWS CLI prerequisite note to S3 setup docs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * Remove AWS CLI prerequisite note from S3 docs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
* chore(dashboard): dockerize visual regression tests for cross-OS consistency Run Playwright browser inside a Docker container so visual screenshot tests produce identical results on macOS (local dev) and Linux (CI). - Add docker-compose.visual.yml with Playwright server container - Update vitest config to connect to Docker browser via websocket when PW_TEST_CONNECT_WS_ENDPOINT env var is set - Add test:visual and test:visual:update npm scripts - Update CI workflow to use Docker Playwright server instead of bare Playwright install - Regenerate screenshot baselines from Linux container Closes PRD-6191 Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review feedback - Add explicit failure handling if Playwright server doesn't start - Bind Docker port to 127.0.0.1 only (don't expose to network) - Align npx playwright version with Docker image (both 1.58.0) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: restore Playwright install step in CI The @vitest/browser-playwright package requires a local Playwright install to initialize, even when the actual browser runs in Docker via connectOptions. Keep the install step alongside the Docker server. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(dashboard): pass PW_TEST_CONNECT_WS_ENDPOINT through turbo strict mode Turbo v2 strict mode filters env vars not listed in turbo.json from child processes. The Playwright WebSocket endpoint was being silently dropped, causing vitest to fall back to local Chromium instead of the Docker server — producing mismatched screenshots in CI. Also pins docker-compose.visual.yml to linux/amd64 so local baselines match CI regardless of host architecture (see microsoft/playwright#13873), and fixes the Playwright cache restore-keys prefix mismatch. Co-Authored-By: Claude Opus 4.6 <[email protected]> * docs: add visual regression test workflow to AGENTS.md * fix(dashboard): resolve Monaco strict locator violation in nested error state visual test * fix(dashboard): fix Monaco strict locator violation with data-testid and stable render wait * Refactor vitest.config.ts by removing unused code * Update vitest.config.ts * fix(dashboard): restore onUnhandledError handler for Monaco browser tests The previous refactor removed the onUnhandledError handler, causing CI to fail with exit code 1 due to 3 known, unfixable Monaco Editor errors in Vitest browser mode: 1. "Cannot use import statement outside a module" - Monaco web workers cannot load ESM in the Vitest browser sandbox 2. "InvalidCharacterError" / "is not a valid name" - Monaco attempts createElement with an SVG data URI as the tag name 3. "Closing rpc while" - Vitest worker RPC shutdown race condition These errors were originally suppressed by Nick in #2046 and #2078 after investigation confirmed they are unfixable Monaco/Vitest internals that do not affect test correctness. Refs: #2046, #2078 * fix(dashboard): remove unused pixelmatch devDependency The pixelmatch package is no longer imported after the vitest.config.ts refactor removed the custom tolerantPixelmatch comparator. Knip correctly flags it as unused. * fix * upd * upd * format * lock * rm * fix * Create fluffy-gorillas-joke.md * Apply suggestion from @claude[bot] Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> * fix(@inkeep/agents-work-apps): mark `@slack/socket-mode` as `dependency` (#2951) * upd * upd * Apply suggestion from @dimaMachina * Apply suggestion from @dimaMachina * Create breezy-lemons-dream.md * Document MCP header forwarding in Visual Builder docs (#2956) * docs: add MCP header forwarding and fix header key casing examples * updated warnings in headers docs * updated warnings in mcp servers docs * In product copilot tutorial (#2957) * docs build updated api reference * tutorial done * implements pnpm minimumReleaseAge and upgrades pnpm to 10.33.0 (#2958) * implements pnpm minimumReleaseAge * upgrades pnpm to 10.16.0 * upgrades pnpm to 10.33.0 * ci: surface stable preview URLs in PRs (#2799) * ci: surface stable preview urls in PRs * fix: add temp file cleanup trap and paginate comment search - Add EXIT trap to clean up mktemp file - Paginate through all PR comments when searching for the existing marker comment, fixing duplicate-comment risk on PRs with 100+ comments Co-authored-by: Andrew Mikofalvy <[email protected]> Co-Authored-By: Claude Opus 4.6 <[email protected]> * ci: fix preview URL comment updates --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Andrew Mikofalvy <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> * feat: S3 presigned URLs for private media delivery (#2887) * feat: add S3 presigned URL support for private media delivery - Add optional getPresignedUrl() to BlobStorageProvider interface - Implement in S3BlobStorageProvider using @aws-sdk/s3-request-presigner - Make resolveMessageBlobUris() async with presigned URL first, manage proxy fallback for non-S3 backends (Option D hybrid) - Update both call sites (run + manage conversation routes) with await - Add presigned URL tests to s3-provider and resolve-blob-uris test suites - Include full spec with evidence files Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * docs: add S3 blob storage deployment guide Covers S3 setup, IAM permissions, env vars, S3-compatible services, storage backend priority, and presigned URL delivery flow. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * chore: add changeset for S3 presigned URL support Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: add error handling for presigned URL failures with proxy fallback - Wrap getPresignedUrl() in try-catch so failures fall through to manage proxy URL instead of crashing the entire conversation response - Add test for presigned URL failure → proxy fallback path - Add mixed-content test with presigned URLs active - Fix doc icon quoting convention Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: correct callout type and remove inaccurate configurable claim in docs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * feat: make presigned URL expiry configurable via `BLOB_STORAGE_PRESIGNED_URL_EXPIRY_SECONDS` - Add `BLOB_STORAGE_PRESIGNED_URL_EXPIRY_SECONDS` to env.ts Zod schema (default 7200s / 2 hours, range 60–604800) - Replace hardcoded `DEFAULT_PRESIGNED_EXPIRY_SECONDS` constant in s3-provider.ts with env var lookup - Update tests to use env var in mocks and verify new default - Add env var to .env.example files and deployment docs * fix: address PR review comments - Clarify Vercel Blob is also a valid production backend (serves via proxy) - Add per-service S3-compatible path-style guidance (R2 vs B2 vs Spaces) - Make first resolve-blob-uris test explicitly set its mock (test isolation) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * chore: reset lockfile from main to minimize resolution drift Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: pin s3-request-presigner to match client-s3 to prevent lockfile drift Pin @aws-sdk/s3-request-presigner to 3.995.0 (same as resolved client-s3) to minimize pnpm-lock.yaml changes and prevent react version mismatch that caused agents-email test failures in CI. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * Resolve blob storage provider once per message list instead of per message Addresses PR feedback: call getBlobStorageProvider() once in resolveMessagesListBlobUris and pass the provider through to resolveMessageBlobUris, avoiding N singleton lookups per conversation retrieval. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * chore: reset lockfile from main and reinstall to fix CI Reset pnpm-lock.yaml from main per repo guidelines to prevent resolution drift that was causing monaco-editor ESM import failures in agents-manage-ui tests. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * Address remaining review suggestions: list-level presigned URL test and AWS CLI prerequisite - Add test for resolveMessagesListBlobUris with presigned URLs active across multiple messages, covering Promise.all handling - Add AWS CLI prerequisite note to S3 setup docs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * Remove AWS CLI prerequisite note from S3 docs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> * ci: add preview janitor and recreate control (#2930) * ci: add preview state janitor and recreate path * ci: simplify preview janitor and var resolution * ci: address preview janitor review feedback * ci: gate preview auth on SpiceDB deployment readiness * ci: tighten preview bootstrap retry budget * ci: retry preview recreate after Railway delete * ci: clarify skipped preview workflow jobs * Rename headers in schema and usage to hyphen format (#2962) * Version Packages (#2952) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Andrew Mikofalvy <[email protected]> * Update actions/setup-node and pnpm/action-setup to Node.js 24 versions (#2963) Upgrade actions/setup-node from v4 to v6.3.0 and pnpm/action-setup from v4 to v5.0.0 across all workflows to resolve the Node.js 20 deprecation warning. Node.js 20 actions will be forced to Node.js 24 starting June 2, 2026. https://claude.ai/code/session_01D5Ah1eAYvZCS2SfZ5Lopi3 Co-authored-by: Claude <[email protected]> * credential id reference added (#2967) * Add minimumReleaseAgeExclude for @inkeep/* packages (#2968) Excludes @inkeep scoped packages from the 1440-minute minimum release age gate so our own published packages can be installed immediately after release. https://claude.ai/code/session_01LBEnpfsjj6r4cdwXs2VeHx Co-authored-by: Claude <[email protected]> * add TooltipProvider * add back timeout * should fix tests * polish * fix * pnpm i * upd --------- Co-authored-by: Varun Varahabhotla <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Varun Varahabhotla <[email protected]> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: bryan-inkeep <[email protected]> Co-authored-by: Gaurav Varma <[email protected]> Co-authored-by: robert-inkeep <[email protected]> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Andrew Mikofalvy <[email protected]> Co-authored-by: Andrew Mikofalvy <[email protected]> Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> Co-authored-by: inkeep-internal-ci[bot] <259778081+inkeep-internal-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
sample conversation response with presigned urls
BLOB_STORAGE_S3_BUCKETis configured,resolveMessageBlobUris()generates presignedGetObjectURLs (1hr expiry) via@aws-sdk/s3-request-presigner/managemedia proxy URLs — no behavior changeChanges
blob-storage/types.tsgetPresignedUrl?()toBlobStorageProviderinterfaceblob-storage/s3-provider.tsgetPresignedUrlusing@aws-sdk/s3-request-presignerblob-storage/resolve-blob-uris.tsrun/routes/conversations.tsawaittoresolveMessagesListBlobUriscallmanage/routes/conversations.tsawaittoresolveMessagesListBlobUriscallagents-api/package.json@aws-sdk/s3-request-presigner, align@aws-sdk/client-s3versionresolve-blob-uris.test.tss3-provider.test.tss3-blob-storage.mdxTest plan
pnpm typecheckpassespnpm lintpassesSpec
See
specs/2026-03-23-vercel-private-blob-presigned-urls/SPEC.md— Option D (Hybrid) selected. Supersedesspecs/2026-03-19-run-media-signed-proxy/SPEC.md.🤖 Generated with Claude Code