Skip to content

Object Storage seamless migration#3275

Draft
matt-aitken wants to merge 11 commits intomainfrom
object-storage-migration
Draft

Object Storage seamless migration#3275
matt-aitken wants to merge 11 commits intomainfrom
object-storage-migration

Conversation

@matt-aitken
Copy link
Copy Markdown
Member

This allows seamless migration to different object storage.

Existing runs that have offloaded payloads/outputs will continue to use the default object store (configured using OBJECT_STORE_* env vars).

You can add additional stores by setting new env vars:

  • OBJECT_STORE_DEFAULT_PROTOCOL this determines where new run large payloads will get stored.
  • If you set that you need to set new env vars for that protocol.

Example:

OBJECT_STORE_DEFAULT_PROTOCOL=“s3"
OBJECT_STORE_S3_BASE_URL=https://s3.us-east-1.amazonaws.com
OBJECT_STORE_S3_ACCESS_KEY_ID=<val>
OBJECT_STORE_S3_SECRET_ACCESS_KEY=<val>
OBJECT_STORE_S3_REGION=us-east-1
OBJECT_STORE_S3_SERVICE=s3

@mintlify
Copy link
Copy Markdown
Contributor

mintlify bot commented Mar 26, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
trigger 🟢 Ready View Preview Mar 26, 2026, 10:40 AM

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 26, 2026

⚠️ No Changeset found

Latest commit: dd0891e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

Replaces the R2-specific object-store implementation with a new multi-protocol object store module (apps/webapp/app/v3/objectStore.server.ts) and deletes the old r2.server.ts. Import sites were updated to use the new module and callsites now capture returned stored filenames. The new module provides protocol-prefixed URI parsing/formatting, a protocol-aware client registry, upload/download/presign APIs, and support for a configurable default upload protocol. Environment examples and documentation were extended with OBJECT_STORE_DEFAULT_PROTOCOL and protocol-prefixed provider variables (e.g., OBJECT_STORE_S3_*, OBJECT_STORE_R2_*).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Object Storage seamless migration' directly and clearly describes the main change—adding support for seamless migration to different object storage providers with multi-provider configuration.
Description check ✅ Passed The description provides a clear overview of the feature (seamless migration, multi-provider support, env var configuration) and includes a practical example, but is missing several template sections including explicit testing steps, a formal changelog entry, and the contributor checklist.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch object-storage-migration

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 and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

🧭 Helm Chart Prerelease Published

Version: 4.0.5-pr3275.dd0891e

Install:

helm upgrade --install trigger \
  oci://ghcr.io/triggerdotdev/charts/trigger \
  --version "4.0.5-pr3275.dd0891e"

⚠️ This is a prerelease for testing. Do not use in production.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/webapp/app/v3/objectStore.server.ts`:
- Around line 24-35: parseStorageUri currently only matches lowercase protocols
so mixed/uppercase protocols (e.g. "S3://...") become mis-parsed later; update
parseStorageUri to normalize the protocol to a canonical case (lowercase) before
returning and add a zod schema to validate/normalize the incoming URI at the
boundary so callers cannot persist an uppercased protocol; ensure the same
normalization/validation is applied where you format URIs (referenced around the
other block lines and constants like OBJECT_STORE_DEFAULT_PROTOCOL and
getObjectStoreConfig) so stored URIs always use the normalized protocol.
- Around line 362-369: The debug log is leaking live presigned credentials by
logging signed.url and raw signed.headers; update the logger.debug call so it
never emits signed.url or full header values—either remove those fields entirely
or replace them with redacted placeholders (e.g., "<REDACTED_URL>" and masked
header keys), and instead log safe metadata such as expiry/TTL or a boolean
indicating presence of auth headers; specifically change the logger.debug
invocation that references signed.url and Object.fromEntries(signed.headers) to
omit or mask those values while keeping projectRef, envSlug, filename, and
protocol.
- Around line 212-215: The logger call inside the packet.data guard currently
serializes the full environment; instead sanitize and log only non-sensitive
identifiers: replace the environment object in the logger.error call with a
minimal payload like { projectExternalRef: environment?.project?.externalRef,
slug: environment?.slug, packetShape: { id: packet?.id, type: packet?.type,
hasData: Boolean(packet?.data) } } so credentials are not emitted; update the
logger.error invocation (the call that currently passes { packet, environment })
to pass this small identifier set and the packet shape only.
- Around line 67-87: The getObjectStoreConfig function currently reads
process.env directly; import the env export from app/env.server.ts and use
env.server to read the protocol-specific keys (e.g.,
env.server[`${prefix}BASE_URL`], env.server[`${prefix}ACCESS_KEY_ID`], etc.)
instead of process.env so the values go through the app's env schema/defaults;
keep the same validation (return undefined if required keys missing) and ensure
the service default ("s3") is applied when service is not set.

In `@apps/webapp/test/objectStore.test.ts`:
- Around line 5-10: The test suite imports functions
(downloadPacketFromObjectStore, uploadPacketToObjectStore, formatStorageUri,
parseStorageUri) from ~/v3/objectStore.server which itself imports ~/env.server
at module load, making tests order-dependent; refactor objectStore.server to not
read env at import time by exporting a factory (e.g., createObjectStoreClient or
initObjectStore) that accepts configuration/options (provider, credentials,
bucket, protocol) and update the tests to call that factory with explicit config
(or dynamically import the module after setting process.env) so tests pass
configuration via options rather than relying on env.server at module load;
ensure functions currently exported are either methods on the returned client or
thin wrappers that accept the config argument so tests can control provider
deterministically.
- Around line 54-56: The test saves a mutable reference with const originalEnv =
process.env so later restores reassign the already-mutated object and env leaks;
fix by cloning and restoring per test: in the test suite use beforeEach to save
a shallow copy (e.g., const savedEnv = { ...process.env } or
structuredClone(process.env)) and afterEach to restore it (replace process.env
with the saved copy or clear/assign keys back), and update the same pattern
around the other occurrence referenced (lines 256–259) so each test gets an
isolated process.env and no state leaks between tests; reference the originalEnv
constant and the afterAll/afterEach teardown code when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: caea1762-24f5-4516-8045-952b2d93eb7f

📥 Commits

Reviewing files that changed from the base of the PR and between c907914 and e77be6b.

📒 Files selected for processing (8)
  • apps/webapp/app/runEngine/services/batchTrigger.server.ts
  • apps/webapp/app/v3/objectStore.server.ts
  • apps/webapp/app/v3/services/batchTriggerV3.server.ts
  • apps/webapp/app/v3/services/triggerTaskV1.server.ts
  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
  • internal-packages/testcontainers/src/minio.ts
  • internal-packages/testcontainers/src/utils.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/webapp/app/runEngine/services/batchTrigger.server.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/webapp/app/v3/services/triggerTaskV1.server.ts
  • apps/webapp/app/v3/services/batchTriggerV3.server.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (14)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: For apps and internal packages (apps/*, internal-packages/*), use pnpm run typecheck --filter <package> for verification, never use build as it proves almost nothing about correctness
Use testcontainers helpers (redisTest, postgresTest, containerTest from @internal/testcontainers) for integration tests with Redis and PostgreSQL instead of mocking
When writing Trigger.dev tasks, always import from @trigger.dev/sdk - never use @trigger.dev/sdk/v3 or deprecated client.defineJob

Files:

  • internal-packages/testcontainers/src/utils.ts
  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
  • internal-packages/testcontainers/src/minio.ts
  • apps/webapp/app/v3/objectStore.server.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

**/*.{ts,tsx,js,jsx}: Use pnpm for package management in this monorepo (version 10.23.0) with Turborepo for orchestration - run commands from root with pnpm run
Add crumbs as you write code for debug tracing using // @Crumbs comments or `// `#region` `@crumbs blocks - they stay on the branch throughout development and are stripped via agentcrumbs strip before merge

Files:

  • internal-packages/testcontainers/src/utils.ts
  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
  • internal-packages/testcontainers/src/minio.ts
  • apps/webapp/app/v3/objectStore.server.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • internal-packages/testcontainers/src/utils.ts
  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
  • internal-packages/testcontainers/src/minio.ts
  • apps/webapp/app/v3/objectStore.server.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • internal-packages/testcontainers/src/utils.ts
  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
  • internal-packages/testcontainers/src/minio.ts
  • apps/webapp/app/v3/objectStore.server.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/test/objectStore.test.ts
  • apps/webapp/app/v3/objectStore.server.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

Files:

  • apps/webapp/test/objectStore.test.ts
apps/webapp/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Test files should only import classes and functions from app/**/*.ts files and should not import env.server.ts directly or indirectly; pass configuration through options instead

Files:

  • apps/webapp/test/objectStore.test.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/test/objectStore.test.ts
  • apps/webapp/app/v3/objectStore.server.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptive describe and it blocks
Tests should avoid mocks or stubs and use the helpers from @internal/testcontainers when Redis or Postgres are needed
Use vitest for running unit tests

**/*.test.{ts,tsx,js,jsx}: Use vitest exclusively for testing and never mock anything - use testcontainers instead for Redis/PostgreSQL
Place test files next to source files using naming convention: MyService.ts -> MyService.test.ts

Files:

  • apps/webapp/test/objectStore.test.ts
apps/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying only server components (apps/webapp/, apps/supervisor/, etc.) with no package changes, add a .server-changes/ file instead of a changeset

Files:

  • apps/webapp/test/objectStore.test.ts
  • apps/webapp/app/v3/objectStore.server.ts
apps/webapp/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

For testable code, never import env.server.ts in test files. Pass configuration as options/constructor arguments instead

Files:

  • apps/webapp/test/objectStore.test.ts
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/v3/objectStore.server.ts
apps/webapp/app/v3/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

In the webapp v3 directory, only modify V2 code paths when encountering V1/V2 branching in services - all new work uses Run Engine 2.0 (@internal/run-engine) and redis-worker, not legacy V1 engine code

Files:

  • apps/webapp/app/v3/objectStore.server.ts
apps/webapp/app/**/*.{ts,tsx,server.ts}

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Access environment variables via env export from app/env.server.ts. Never use process.env directly

Files:

  • apps/webapp/app/v3/objectStore.server.ts
🧠 Learnings (14)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:24:14.566Z
Learning: Applies to apps/webapp/app/v3/**/*.{ts,tsx} : In the webapp v3 directory, only modify V2 code paths when encountering V1/V2 branching in services - all new work uses Run Engine 2.0 (`internal/run-engine`) and redis-worker, not legacy V1 engine code
📚 Learning: 2026-03-23T06:24:14.566Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:24:14.566Z
Learning: Applies to **/*.{ts,tsx} : Use testcontainers helpers (`redisTest`, `postgresTest`, `containerTest` from `internal/testcontainers`) for integration tests with Redis and PostgreSQL instead of mocking

Applied to files:

  • internal-packages/testcontainers/src/utils.ts
  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
  • internal-packages/testcontainers/src/minio.ts
📚 Learning: 2026-03-02T12:43:25.254Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: internal-packages/run-engine/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:25.254Z
Learning: Applies to internal-packages/run-engine/src/engine/tests/**/*.test.ts : Implement tests for RunEngine in `src/engine/tests/` using testcontainers for Redis and PostgreSQL containerization

Applied to files:

  • internal-packages/testcontainers/src/utils.ts
  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
  • internal-packages/testcontainers/src/minio.ts
📚 Learning: 2026-01-15T10:48:02.687Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T10:48:02.687Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Tests should avoid mocks or stubs and use the helpers from `internal/testcontainers` when Redis or Postgres are needed

Applied to files:

  • internal-packages/testcontainers/src/utils.ts
  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
📚 Learning: 2026-03-22T13:26:12.060Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3244
File: apps/webapp/app/components/code/TextEditor.tsx:81-86
Timestamp: 2026-03-22T13:26:12.060Z
Learning: In the triggerdotdev/trigger.dev codebase, do not flag `navigator.clipboard.writeText(...)` calls for `missing-await`/`unhandled-promise` issues. These clipboard writes are intentionally invoked without `await` and without `catch` handlers across the project; keep that behavior consistent when reviewing TypeScript/TSX files (e.g., usages like in `apps/webapp/app/components/code/TextEditor.tsx`).

Applied to files:

  • internal-packages/testcontainers/src/utils.ts
  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
  • internal-packages/testcontainers/src/minio.ts
  • apps/webapp/app/v3/objectStore.server.ts
📚 Learning: 2026-03-22T19:24:14.403Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3187
File: apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts:200-204
Timestamp: 2026-03-22T19:24:14.403Z
Learning: In the triggerdotdev/trigger.dev codebase, webhook URLs are not expected to contain embedded credentials/secrets (e.g., fields like `ProjectAlertWebhookProperties` should only hold credential-free webhook endpoints). During code review, if you see logging or inclusion of raw webhook URLs in error messages, do not automatically treat it as a credential-leak/secrets-in-logs issue by default—first verify the URL does not contain embedded credentials (for example, no username/password in the URL, no obvious secret/token query params or fragments). If the URL is credential-free per this project’s conventions, allow the logging.

Applied to files:

  • internal-packages/testcontainers/src/utils.ts
  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
  • internal-packages/testcontainers/src/minio.ts
  • apps/webapp/app/v3/objectStore.server.ts
📚 Learning: 2026-03-23T06:24:14.566Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:24:14.566Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vitest exclusively for testing and never mock anything - use testcontainers instead for Redis/PostgreSQL

Applied to files:

  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Use vitest for all tests in the Trigger.dev repository

Applied to files:

  • apps/webapp/test/objectStore.test.ts
📚 Learning: 2026-01-15T10:48:02.687Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T10:48:02.687Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vitest for running unit tests

Applied to files:

  • apps/webapp/test/objectStore.test.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/services/**/*.server.{ts,tsx} : Separate testable services from configuration files; follow the pattern of `realtimeClient.server.ts` (testable service) and `realtimeClientGlobal.server.ts` (configuration) in the webapp

Applied to files:

  • apps/webapp/test/objectStore.test.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to internal-packages/database/**/*.{ts,tsx} : Use Prisma for database interactions in internal-packages/database with PostgreSQL

Applied to files:

  • apps/webapp/test/objectStore.test.ts
📚 Learning: 2026-03-02T12:43:43.173Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/redis-worker/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:43.173Z
Learning: Applies to packages/redis-worker/**/redis-worker/**/*.{test,spec}.{ts,tsx} : Use testcontainers for Redis in test files for redis-worker

Applied to files:

  • apps/webapp/test/objectStore.test.ts
  • internal-packages/testcontainers/src/index.ts
  • internal-packages/testcontainers/src/minio.ts
📚 Learning: 2025-07-12T18:00:06.163Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2264
File: apps/webapp/app/utils/searchParams.ts:16-18
Timestamp: 2025-07-12T18:00:06.163Z
Learning: The `objectToSearchParams` function in `apps/webapp/app/utils/searchParams.ts` is used to generate URL parameters from objects and is separate from code that parses incoming search parameters. Changes to this function only affect places where it's used to create URLs, not places that parse search parameters from external sources.

Applied to files:

  • apps/webapp/app/v3/objectStore.server.ts
📚 Learning: 2026-03-26T10:02:22.373Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 3254
File: apps/webapp/app/services/platformNotifications.server.ts:363-385
Timestamp: 2026-03-26T10:02:22.373Z
Learning: In `triggerdotdev/trigger.dev`, the `getNextCliNotification` fallback in `apps/webapp/app/services/platformNotifications.server.ts` intentionally uses `prisma.orgMember.findFirst` (single org) when no `projectRef` is provided. This is acceptable for v1 because the CLI (`dev` and `login` commands) always passes `projectRef` in normal usage, making the fallback a rare edge case. Do not flag the single-org fallback as a multi-org correctness bug in this file.

Applied to files:

  • apps/webapp/app/v3/objectStore.server.ts
🪛 GitHub Actions: 🤖 PR Checks
apps/webapp/test/objectStore.test.ts

[warning] 1-1: Vitest: The CJS build of Vite's Node API is deprecated. See https://vite.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.

apps/webapp/app/v3/objectStore.server.ts

[warning] 1-1: Vitest: The CJS build of Vite's Node API is deprecated. See https://vite.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.

Comment on lines +24 to +35
export function parseStorageUri(uri: string): ParsedStorageUri {
const match = uri.match(/^([a-z0-9]+):\/\/(.+)$/);
if (match) {
return {
protocol: match[1],
path: match[2],
};
}
return {
protocol: undefined,
path: uri,
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Normalize protocol casing before persisting it.

getObjectStoreConfig() treats provider names case-insensitively via toUpperCase(), but parseStorageUri() only matches lowercase protocols. If a caller passes "S3" (or OBJECT_STORE_DEFAULT_PROTOCOL is uppercase), you'll store S3://... and later parse it as a legacy path on download. Normalize or validate the protocol once at the boundary before formatting/parsing it.

As per coding guidelines "Use zod for validation in packages/core and apps/webapp".

Also applies to: 44-48

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/v3/objectStore.server.ts` around lines 24 - 35,
parseStorageUri currently only matches lowercase protocols so mixed/uppercase
protocols (e.g. "S3://...") become mis-parsed later; update parseStorageUri to
normalize the protocol to a canonical case (lowercase) before returning and add
a zod schema to validate/normalize the incoming URI at the boundary so callers
cannot persist an uppercased protocol; ensure the same normalization/validation
is applied where you format URIs (referenced around the other block lines and
constants like OBJECT_STORE_DEFAULT_PROTOCOL and getObjectStoreConfig) so stored
URIs always use the normalized protocol.

Comment on lines +67 to +87
function getObjectStoreConfig(protocol?: string): ObjectStoreConfig | undefined {
if (protocol) {
// Named provider (e.g., OBJECT_STORE_S3_*)
const prefix = `OBJECT_STORE_${protocol.toUpperCase()}_`;
const baseUrl = process.env[`${prefix}BASE_URL`];
const accessKeyId = process.env[`${prefix}ACCESS_KEY_ID`];
const secretAccessKey = process.env[`${prefix}SECRET_ACCESS_KEY`];
const region = process.env[`${prefix}REGION`];
const service = process.env[`${prefix}SERVICE`];

if (!baseUrl || !accessKeyId || !secretAccessKey) {
return undefined;
}

return {
baseUrl,
accessKeyId,
secretAccessKey,
region,
service,
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep named-provider config behind env.server.

This branch reads process.env directly, so it bypasses the webapp env schema/defaults. Protocol-specific providers won't get the same boot-time validation/default handling as the legacy branch, including the new "s3" service default mentioned below.

As per coding guidelines "Access environment variables via env export from app/env.server.ts. Never use process.env directly".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/v3/objectStore.server.ts` around lines 67 - 87, The
getObjectStoreConfig function currently reads process.env directly; import the
env export from app/env.server.ts and use env.server to read the
protocol-specific keys (e.g., env.server[`${prefix}BASE_URL`],
env.server[`${prefix}ACCESS_KEY_ID`], etc.) instead of process.env so the values
go through the app's env schema/defaults; keep the same validation (return
undefined if required keys missing) and ensure the service default ("s3") is
applied when service is not set.

Comment on lines +5 to +10
import {
downloadPacketFromObjectStore,
formatStorageUri,
parseStorageUri,
uploadPacketToObjectStore,
} from "~/v3/objectStore.server";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This suite is coupled to env.server initialization.

~/v3/objectStore.server imports ~/env.server at module load, but the tests mutate process.env later to switch providers. That makes the legacy/default-protocol paths order-dependent and can easily exercise stale config instead of the one the test just set.

As per coding guidelines "Test files should only import classes and functions from app/**/*.ts files and should not import env.server.ts directly or indirectly; pass configuration through options instead".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/test/objectStore.test.ts` around lines 5 - 10, The test suite
imports functions (downloadPacketFromObjectStore, uploadPacketToObjectStore,
formatStorageUri, parseStorageUri) from ~/v3/objectStore.server which itself
imports ~/env.server at module load, making tests order-dependent; refactor
objectStore.server to not read env at import time by exporting a factory (e.g.,
createObjectStoreClient or initObjectStore) that accepts configuration/options
(provider, credentials, bucket, protocol) and update the tests to call that
factory with explicit config (or dynamically import the module after setting
process.env) so tests pass configuration via options rather than relying on
env.server at module load; ensure functions currently exported are either
methods on the returned client or thin wrappers that accept the config argument
so tests can control provider deterministically.

Comment on lines +54 to +56
// Mock env module for testing
const originalEnv = process.env;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clone and reset process.env per test.

const originalEnv = process.env stores the same mutable object, so the afterAll block just reassigns the already-mutated reference. These object-store vars can leak between cases and into later files in the same worker.

♻️ Suggested fix
-import { afterAll, describe, expect, it, vi } from "vitest";
+import { afterEach, describe, expect, it, vi } from "vitest";
@@
-const originalEnv = process.env;
+const originalEnv = { ...process.env };
@@
-afterAll(() => {
-  process.env = originalEnv;
-});
+afterEach(() => {
+  for (const key of Object.keys(process.env)) {
+    delete process.env[key];
+  }
+
+  Object.assign(process.env, originalEnv);
+});

Also applies to: 256-259

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/test/objectStore.test.ts` around lines 54 - 56, The test saves a
mutable reference with const originalEnv = process.env so later restores
reassign the already-mutated object and env leaks; fix by cloning and restoring
per test: in the test suite use beforeEach to save a shallow copy (e.g., const
savedEnv = { ...process.env } or structuredClone(process.env)) and afterEach to
restore it (replace process.env with the saved copy or clear/assign keys back),
and update the same pattern around the other occurrence referenced (lines
256–259) so each test gets an isolated process.env and no state leaks between
tests; reference the originalEnv constant and the afterAll/afterEach teardown
code when making the change.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 new potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +86 to +95
async putObject(key: string, body: ReadableStream | string, contentType: string): Promise<void> {
await this.s3Client.send(
new PutObjectCommand({
Bucket: this.config.bucket,
Key: key,
Body: body as string,
ContentType: contentType,
})
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 AwsSdkClient stores objects with doubled bucket prefix in key, breaking cross-client compatibility

The AwsSdkClient receives a key like packets/proj_ref/env_slug/file.json and passes it directly as the S3 Key with a separate Bucket parameter (e.g., "packets"). This stores the object at Bucket=packets, Key=packets/proj_ref/env_slug/file.json. In contrast, Aws4FetchClient puts the same key into the URL path (/packets/proj_ref/env_slug/file.json), where in path-style S3 addressing the first segment is the bucket, yielding Bucket=packets, Key=proj_ref/env_slug/file.json. This means the same logical key maps to different physical S3 objects depending on which client is used. While a single protocol consistently uses one client type, the generatePresignedRequest function (used by resources.packets.$environmentId.$.ts:37-50) constructs a presigned URL via the AwsSdkClient that points to packets/packets/..., creating a double-prefix path. If the deployment later switches from IAM mode to static credentials (or vice versa) for the same protocol, all previously stored data becomes inaccessible.

Prompt for agents
In apps/webapp/app/v3/objectStoreClient.server.ts, the AwsSdkClient receives full keys like 'packets/proj/env/file.json' but also uses a separate Bucket config (typically 'packets'). This creates a doubled prefix: Bucket='packets', Key='packets/proj/env/file.json'. To fix this, you need to either:

1. Strip the bucket prefix from the key before passing to S3 commands in AwsSdkClient (parse the key to extract the bucket and actual object key), or
2. Change the callers in objectStore.server.ts (uploadPacketToObjectStore, downloadPacketFromObjectStore, generatePresignedRequest, uploadDataToObjectStore) to NOT include the 'packets/' prefix in the key when the AwsSdkClient path is used, or
3. Have ObjectStoreClient.create() accept and store the bucket info, and have the IObjectStoreClient interface handle bucket-aware key construction internally.

The cleanest approach is option 3: let ObjectStoreClient expose a method that builds keys, and have AwsSdkClient strip the first path segment (the bucket name) from the key when it matches self.config.bucket. This ensures both client types address the same physical S3 object.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

new PutObjectCommand({
Bucket: this.config.bucket,
Key: key,
Body: body as string,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 AwsSdkClient.putObject silently casts ReadableStream to string, will fail at runtime for stream uploads

The putObject method accepts body: ReadableStream | string but casts it to string via Body: body as string at line 91. The AWS SDK v3 PutObjectCommand Body field accepts string | Uint8Array | Buffer | Readable | ReadableStream | Blob, so the cast is unnecessary. However, if a ReadableStream is passed, the as string cast tricks TypeScript but at runtime the SDK receives a ReadableStream. While the SDK can handle ReadableStreams, the explicit as string cast is incorrect and could cause subtle issues if the SDK internally checks typeof body === 'string' for content-length calculation or encoding decisions. The uploadPacketToObjectStore function signature accepts ReadableStream | string (apps/webapp/app/v3/objectStore.server.ts:126), so a ReadableStream body reaching this code path is plausible.

Suggested change
Body: body as string,
Body: body,
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +250 to +251
const { protocol, path } = parseStorageUri(filename);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 Protocol prefix in stored data could cause issues with SDK packet URL construction

When OBJECT_STORE_DEFAULT_PROTOCOL=s3, payload data stored in the DB will be s3://run_abc/payload.json. The SDK's conditionallyImportPacket (packages/core/src/v3/utils/ioSerialization.ts:252-263) calls $client.getPayloadUrl(packet.data), which hits api.v1.packets.$.ts with the filename as a URL path segment. The :// characters in s3://run_abc/payload.json may need URL encoding when used in the path. Remix's route parameter decoding should handle this transparently (the * splat param captures and decodes), and parseStorageUri at objectStore.server.ts:250 correctly parses it back. But this depends on how the SDK constructs the API URL — if it doesn't URL-encode the filename, some HTTP clients or proxies might interpret :// as a protocol.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@matt-aitken matt-aitken force-pushed the object-storage-migration branch from b03061c to 346c588 Compare March 30, 2026 11:08
This allows seamless migration to different object storage.
- Fix objectStore test failures: replace manual PrismaClient(process.env.DATABASE_URL)
  with proper postgresAndMinioTest fixture (new combined testcontainer)
- Fix undefined prefix in uploadDataToObjectStore: pathname now conditional
- Remove duplicate MAXIMUM_DEV_QUEUE_SIZE/MAXIMUM_DEPLOYED_QUEUE_SIZE rows from docs
Delegate the object store client to aws4fetch (existing) of the official AWS S3Client for IAM login.
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