[Docs] Add initial files and configuration#3
Merged
Conversation
tejassudsfp
pushed a commit
to tejassudsfp/trigger.dev
that referenced
this pull request
Apr 22, 2026
…riggerBridgeModule With the 4-hop provider cycle now broken (hotfix triggerdotdev#2 made AgentService lazy-resolve RunsBridge via ModuleRef), there's no actual circular dependency between TriggerBridgeModule and ConnectionsModule. The forwardRef wrapper was making ConnectionsGateway's export invisible to RunsBridgeService's @Inject(ConnectionsGateway) at instantiation time, causing "UndefinedDependencyException: argument at index [0]" on every boot. Replace forwardRef(() => ConnectionsModule) with a plain ConnectionsModule import. AgentRuntimeModule's forwardRef stays (topological safety given transitive imports through ConnectionsModule). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
matt-aitken
added a commit
that referenced
this pull request
May 1, 2026
…ker tightening #1 Batch trigger AND semantics (security): `api.v[12].tasks.batch` now uses `everyResource(...)` so a JWT scoped to taskA can no longer submit a batch that also includes taskB / taskC. Added an `everyResource` helper to `apiBuilder` (Symbol-marked wrapper that flips `ability.can` to `every`). Multi-key OR semantics still apply for single-resource arrays (a run carries multiple identifiers). Updated the e2e test to assert AND behaviour. #3 Realtime stream resource (correctness): `findResource` for `realtime.v1.streams.$runId.$streamId` now selects `taskIdentifier`, `runTags`, and `realtimeStreamsVersion` — fields the auth resource builder + handler read but findResource was returning undefined for. #4 projectCreated optional chaining (crash bug): added the missing `?.` between v3Subscription and plan so a missing subscription no longer throws and aborts project creation. #5 RBAC plugin loader logging: distinguish "plugin itself missing" from "plugin found but a transitive dep failed to resolve" by inspecting the ERR_MODULE_NOT_FOUND error message for the plugin's own module specifier. The transitive-dep case now logs at error level (matches the comment's stated behaviour). Removed the orphan log line that contradicted it. #6 account.tokens picker source mismatch: the picker now sources roles from the same plan-tier-filtered list (`systemRoles().filter(available)`) as the default-role calculation. Added server-side roleId revalidation in the create action so a hand-crafted POST can't bind a PAT to an unavailable role.
matt-aitken
added a commit
that referenced
this pull request
May 3, 2026
…ker tightening #1 Batch trigger AND semantics (security): `api.v[12].tasks.batch` now uses `everyResource(...)` so a JWT scoped to taskA can no longer submit a batch that also includes taskB / taskC. Added an `everyResource` helper to `apiBuilder` (Symbol-marked wrapper that flips `ability.can` to `every`). Multi-key OR semantics still apply for single-resource arrays (a run carries multiple identifiers). Updated the e2e test to assert AND behaviour. #3 Realtime stream resource (correctness): `findResource` for `realtime.v1.streams.$runId.$streamId` now selects `taskIdentifier`, `runTags`, and `realtimeStreamsVersion` — fields the auth resource builder + handler read but findResource was returning undefined for. #4 projectCreated optional chaining (crash bug): added the missing `?.` between v3Subscription and plan so a missing subscription no longer throws and aborts project creation. #5 RBAC plugin loader logging: distinguish "plugin itself missing" from "plugin found but a transitive dep failed to resolve" by inspecting the ERR_MODULE_NOT_FOUND error message for the plugin's own module specifier. The transitive-dep case now logs at error level (matches the comment's stated behaviour). Removed the orphan log line that contradicted it. #6 account.tokens picker source mismatch: the picker now sources roles from the same plan-tier-filtered list (`systemRoles().filter(available)`) as the default-role calculation. Added server-side roleId revalidation in the create action so a hand-crafted POST can't bind a PAT to an unavailable role.
matt-aitken
added a commit
that referenced
this pull request
May 4, 2026
…ker tightening #1 Batch trigger AND semantics (security): `api.v[12].tasks.batch` now uses `everyResource(...)` so a JWT scoped to taskA can no longer submit a batch that also includes taskB / taskC. Added an `everyResource` helper to `apiBuilder` (Symbol-marked wrapper that flips `ability.can` to `every`). Multi-key OR semantics still apply for single-resource arrays (a run carries multiple identifiers). Updated the e2e test to assert AND behaviour. #3 Realtime stream resource (correctness): `findResource` for `realtime.v1.streams.$runId.$streamId` now selects `taskIdentifier`, `runTags`, and `realtimeStreamsVersion` — fields the auth resource builder + handler read but findResource was returning undefined for. #4 projectCreated optional chaining (crash bug): added the missing `?.` between v3Subscription and plan so a missing subscription no longer throws and aborts project creation. #5 RBAC plugin loader logging: distinguish "plugin itself missing" from "plugin found but a transitive dep failed to resolve" by inspecting the ERR_MODULE_NOT_FOUND error message for the plugin's own module specifier. The transitive-dep case now logs at error level (matches the comment's stated behaviour). Removed the orphan log line that contradicted it. #6 account.tokens picker source mismatch: the picker now sources roles from the same plan-tier-filtered list (`systemRoles().filter(available)`) as the default-role calculation. Added server-side roleId revalidation in the create action so a hand-crafted POST can't bind a PAT to an unavailable role.
matt-aitken
added a commit
that referenced
this pull request
May 4, 2026
Five real-bug fixes from CodeRabbit + Devin review of #3499: #1 personalAccessToken.server.ts: FALLBACK_NOT_INSTALLED_ERROR string was 'RBAC fallback not installed' but the OSS fallback actually returns 'RBAC plugin not installed'. The mismatch made every PAT creation with a roleId hit the compensating-delete branch on self-hosters with no plugin installed — including the dashboard PAT-creation flow. Aligns the constant with the canonical string. #2 internal-packages/rbac/src/fallback.ts: authenticateBearer skipped the revoked-API-key grace window (RevokedApiKey table), so a freshly-rotated env API key would 401 immediately on the new auth path. Mirrors findEnvironmentByApiKey's fallback-to-RevokedApiKey logic so the auth-cross-cutting e2e tests pass. #3 api.v1.query.ts: multi-table queries built a plain RbacResource array, which checkAuth treats as 'any element passes'. A JWT scoped to one detected table could submit a query that also reads another table it isn't scoped to. Wrap with everyResource — same AND-semantics fix as the batch trigger routes. #4 account.tokens/route.tsx: defaultRoleId could land on a custom or plan-blocked role when userRoleId wasn't in the picker's assignable set. The action's submit-revalidation would then 400 until the user manually changed the dropdown. Clamp the default to roles the picker actually renders. #5 settings.team/route.tsx: the role Select used defaultValue, so a failed set-role submit left the attempted role visible while the server kept the old one. Switch to a controlled value bound to currentRoleId.
matt-aitken
added a commit
that referenced
this pull request
May 6, 2026
#1 internal-packages/rbac/src/ability.ts (severity: 🔴 silent privilege escalation): buildJwtAbility was treating any scope starting with `admin:` as a universal wildcard. Pre-RBAC, the legacy checkAuthorization string-matched superScopes — `admin:sessions` only granted access to routes that explicitly listed it. After the JWT- ability split, the same scope was returning true for every action on every resource. Restrict the bypass to bare `admin` (no second segment); `admin:<type>` now flows through normal matching as action="admin" against resources of that type. Adds 2 regression tests in ability.test.ts. #2 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (status discard): authenticateRequestForApiBuilder hardcoded `status: 401` even though BearerAuthResult.status is `401 | 403`. A plugin returning 403 (e.g. suspended account, IP block) would silently get downgraded to 401 — semantically wrong (401 = "who are you?", 403 = "you're not allowed") and confusing for client retry logic. Plumb result.status through. #3 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (everyResource([]) vacuous truth): [].every() returns true, so everyResource([]) was passing auth for any token. Not exploitable today (Zod rejects empty bodies before auth), but the auth layer should never grant on empty input. Same defensive guard added to anyResource() for symmetry — only PERMISSIVE_ABILITY would have granted there, but the pattern shouldn't depend on the ability's choice. #4 internal-packages/rbac/src/fallback.ts (PREVIEW env regression): the fallback's authenticateBearer looked up environments by apiKey only, skipping the branch-aware resolution that findEnvironmentByApiKey does for PREVIEW envs. Self-hosters using preview/branch envs would either fail or operate against the parent env. Mirror the legacy path: read x-trigger-branch, include matching child env, and pivot the resolved env to the child (apiKey/orgMember/organization/project inherited from parent). sanitizeBranchName inlined here because internal-packages can't import webapp code; comment notes the duplication. All four flagged by Devin's PR review. Cloud plugin's buildJwtAbility gets the same #1 fix in a sibling commit on this PR's cloud branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
matt-aitken
added a commit
that referenced
this pull request
May 6, 2026
…ker tightening #1 Batch trigger AND semantics (security): `api.v[12].tasks.batch` now uses `everyResource(...)` so a JWT scoped to taskA can no longer submit a batch that also includes taskB / taskC. Added an `everyResource` helper to `apiBuilder` (Symbol-marked wrapper that flips `ability.can` to `every`). Multi-key OR semantics still apply for single-resource arrays (a run carries multiple identifiers). Updated the e2e test to assert AND behaviour. #3 Realtime stream resource (correctness): `findResource` for `realtime.v1.streams.$runId.$streamId` now selects `taskIdentifier`, `runTags`, and `realtimeStreamsVersion` — fields the auth resource builder + handler read but findResource was returning undefined for. #4 projectCreated optional chaining (crash bug): added the missing `?.` between v3Subscription and plan so a missing subscription no longer throws and aborts project creation. #5 RBAC plugin loader logging: distinguish "plugin itself missing" from "plugin found but a transitive dep failed to resolve" by inspecting the ERR_MODULE_NOT_FOUND error message for the plugin's own module specifier. The transitive-dep case now logs at error level (matches the comment's stated behaviour). Removed the orphan log line that contradicted it. #6 account.tokens picker source mismatch: the picker now sources roles from the same plan-tier-filtered list (`systemRoles().filter(available)`) as the default-role calculation. Added server-side roleId revalidation in the create action so a hand-crafted POST can't bind a PAT to an unavailable role.
matt-aitken
added a commit
that referenced
this pull request
May 6, 2026
Five real-bug fixes from CodeRabbit + Devin review of #3499: #1 personalAccessToken.server.ts: FALLBACK_NOT_INSTALLED_ERROR string was 'RBAC fallback not installed' but the OSS fallback actually returns 'RBAC plugin not installed'. The mismatch made every PAT creation with a roleId hit the compensating-delete branch on self-hosters with no plugin installed — including the dashboard PAT-creation flow. Aligns the constant with the canonical string. #2 internal-packages/rbac/src/fallback.ts: authenticateBearer skipped the revoked-API-key grace window (RevokedApiKey table), so a freshly-rotated env API key would 401 immediately on the new auth path. Mirrors findEnvironmentByApiKey's fallback-to-RevokedApiKey logic so the auth-cross-cutting e2e tests pass. #3 api.v1.query.ts: multi-table queries built a plain RbacResource array, which checkAuth treats as 'any element passes'. A JWT scoped to one detected table could submit a query that also reads another table it isn't scoped to. Wrap with everyResource — same AND-semantics fix as the batch trigger routes. #4 account.tokens/route.tsx: defaultRoleId could land on a custom or plan-blocked role when userRoleId wasn't in the picker's assignable set. The action's submit-revalidation would then 400 until the user manually changed the dropdown. Clamp the default to roles the picker actually renders. #5 settings.team/route.tsx: the role Select used defaultValue, so a failed set-role submit left the attempted role visible while the server kept the old one. Switch to a controlled value bound to currentRoleId.
matt-aitken
added a commit
that referenced
this pull request
May 6, 2026
#1 internal-packages/rbac/src/ability.ts (severity: 🔴 silent privilege escalation): buildJwtAbility was treating any scope starting with `admin:` as a universal wildcard. Pre-RBAC, the legacy checkAuthorization string-matched superScopes — `admin:sessions` only granted access to routes that explicitly listed it. After the JWT- ability split, the same scope was returning true for every action on every resource. Restrict the bypass to bare `admin` (no second segment); `admin:<type>` now flows through normal matching as action="admin" against resources of that type. Adds 2 regression tests in ability.test.ts. #2 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (status discard): authenticateRequestForApiBuilder hardcoded `status: 401` even though BearerAuthResult.status is `401 | 403`. A plugin returning 403 (e.g. suspended account, IP block) would silently get downgraded to 401 — semantically wrong (401 = "who are you?", 403 = "you're not allowed") and confusing for client retry logic. Plumb result.status through. #3 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (everyResource([]) vacuous truth): [].every() returns true, so everyResource([]) was passing auth for any token. Not exploitable today (Zod rejects empty bodies before auth), but the auth layer should never grant on empty input. Same defensive guard added to anyResource() for symmetry — only PERMISSIVE_ABILITY would have granted there, but the pattern shouldn't depend on the ability's choice. #4 internal-packages/rbac/src/fallback.ts (PREVIEW env regression): the fallback's authenticateBearer looked up environments by apiKey only, skipping the branch-aware resolution that findEnvironmentByApiKey does for PREVIEW envs. Self-hosters using preview/branch envs would either fail or operate against the parent env. Mirror the legacy path: read x-trigger-branch, include matching child env, and pivot the resolved env to the child (apiKey/orgMember/organization/project inherited from parent). sanitizeBranchName inlined here because internal-packages can't import webapp code; comment notes the duplication. All four flagged by Devin's PR review. Cloud plugin's buildJwtAbility gets the same #1 fix in a sibling commit on this PR's cloud branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
matt-aitken
added a commit
that referenced
this pull request
May 6, 2026
…ker tightening #1 Batch trigger AND semantics (security): `api.v[12].tasks.batch` now uses `everyResource(...)` so a JWT scoped to taskA can no longer submit a batch that also includes taskB / taskC. Added an `everyResource` helper to `apiBuilder` (Symbol-marked wrapper that flips `ability.can` to `every`). Multi-key OR semantics still apply for single-resource arrays (a run carries multiple identifiers). Updated the e2e test to assert AND behaviour. #3 Realtime stream resource (correctness): `findResource` for `realtime.v1.streams.$runId.$streamId` now selects `taskIdentifier`, `runTags`, and `realtimeStreamsVersion` — fields the auth resource builder + handler read but findResource was returning undefined for. #4 projectCreated optional chaining (crash bug): added the missing `?.` between v3Subscription and plan so a missing subscription no longer throws and aborts project creation. #5 RBAC plugin loader logging: distinguish "plugin itself missing" from "plugin found but a transitive dep failed to resolve" by inspecting the ERR_MODULE_NOT_FOUND error message for the plugin's own module specifier. The transitive-dep case now logs at error level (matches the comment's stated behaviour). Removed the orphan log line that contradicted it. #6 account.tokens picker source mismatch: the picker now sources roles from the same plan-tier-filtered list (`systemRoles().filter(available)`) as the default-role calculation. Added server-side roleId revalidation in the create action so a hand-crafted POST can't bind a PAT to an unavailable role.
matt-aitken
added a commit
that referenced
this pull request
May 6, 2026
Five real-bug fixes from CodeRabbit + Devin review of #3499: #1 personalAccessToken.server.ts: FALLBACK_NOT_INSTALLED_ERROR string was 'RBAC fallback not installed' but the OSS fallback actually returns 'RBAC plugin not installed'. The mismatch made every PAT creation with a roleId hit the compensating-delete branch on self-hosters with no plugin installed — including the dashboard PAT-creation flow. Aligns the constant with the canonical string. #2 internal-packages/rbac/src/fallback.ts: authenticateBearer skipped the revoked-API-key grace window (RevokedApiKey table), so a freshly-rotated env API key would 401 immediately on the new auth path. Mirrors findEnvironmentByApiKey's fallback-to-RevokedApiKey logic so the auth-cross-cutting e2e tests pass. #3 api.v1.query.ts: multi-table queries built a plain RbacResource array, which checkAuth treats as 'any element passes'. A JWT scoped to one detected table could submit a query that also reads another table it isn't scoped to. Wrap with everyResource — same AND-semantics fix as the batch trigger routes. #4 account.tokens/route.tsx: defaultRoleId could land on a custom or plan-blocked role when userRoleId wasn't in the picker's assignable set. The action's submit-revalidation would then 400 until the user manually changed the dropdown. Clamp the default to roles the picker actually renders. #5 settings.team/route.tsx: the role Select used defaultValue, so a failed set-role submit left the attempted role visible while the server kept the old one. Switch to a controlled value bound to currentRoleId.
matt-aitken
added a commit
that referenced
this pull request
May 6, 2026
#1 internal-packages/rbac/src/ability.ts (severity: 🔴 silent privilege escalation): buildJwtAbility was treating any scope starting with `admin:` as a universal wildcard. Pre-RBAC, the legacy checkAuthorization string-matched superScopes — `admin:sessions` only granted access to routes that explicitly listed it. After the JWT- ability split, the same scope was returning true for every action on every resource. Restrict the bypass to bare `admin` (no second segment); `admin:<type>` now flows through normal matching as action="admin" against resources of that type. Adds 2 regression tests in ability.test.ts. #2 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (status discard): authenticateRequestForApiBuilder hardcoded `status: 401` even though BearerAuthResult.status is `401 | 403`. A plugin returning 403 (e.g. suspended account, IP block) would silently get downgraded to 401 — semantically wrong (401 = "who are you?", 403 = "you're not allowed") and confusing for client retry logic. Plumb result.status through. #3 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (everyResource([]) vacuous truth): [].every() returns true, so everyResource([]) was passing auth for any token. Not exploitable today (Zod rejects empty bodies before auth), but the auth layer should never grant on empty input. Same defensive guard added to anyResource() for symmetry — only PERMISSIVE_ABILITY would have granted there, but the pattern shouldn't depend on the ability's choice. #4 internal-packages/rbac/src/fallback.ts (PREVIEW env regression): the fallback's authenticateBearer looked up environments by apiKey only, skipping the branch-aware resolution that findEnvironmentByApiKey does for PREVIEW envs. Self-hosters using preview/branch envs would either fail or operate against the parent env. Mirror the legacy path: read x-trigger-branch, include matching child env, and pivot the resolved env to the child (apiKey/orgMember/organization/project inherited from parent). sanitizeBranchName inlined here because internal-packages can't import webapp code; comment notes the duplication. All four flagged by Devin's PR review. Cloud plugin's buildJwtAbility gets the same #1 fix in a sibling commit on this PR's cloud branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
matt-aitken
added a commit
that referenced
this pull request
May 8, 2026
…ker tightening #1 Batch trigger AND semantics (security): `api.v[12].tasks.batch` now uses `everyResource(...)` so a JWT scoped to taskA can no longer submit a batch that also includes taskB / taskC. Added an `everyResource` helper to `apiBuilder` (Symbol-marked wrapper that flips `ability.can` to `every`). Multi-key OR semantics still apply for single-resource arrays (a run carries multiple identifiers). Updated the e2e test to assert AND behaviour. #3 Realtime stream resource (correctness): `findResource` for `realtime.v1.streams.$runId.$streamId` now selects `taskIdentifier`, `runTags`, and `realtimeStreamsVersion` — fields the auth resource builder + handler read but findResource was returning undefined for. #4 projectCreated optional chaining (crash bug): added the missing `?.` between v3Subscription and plan so a missing subscription no longer throws and aborts project creation. #5 RBAC plugin loader logging: distinguish "plugin itself missing" from "plugin found but a transitive dep failed to resolve" by inspecting the ERR_MODULE_NOT_FOUND error message for the plugin's own module specifier. The transitive-dep case now logs at error level (matches the comment's stated behaviour). Removed the orphan log line that contradicted it. #6 account.tokens picker source mismatch: the picker now sources roles from the same plan-tier-filtered list (`systemRoles().filter(available)`) as the default-role calculation. Added server-side roleId revalidation in the create action so a hand-crafted POST can't bind a PAT to an unavailable role.
matt-aitken
added a commit
that referenced
this pull request
May 8, 2026
Five real-bug fixes from CodeRabbit + Devin review of #3499: #1 personalAccessToken.server.ts: FALLBACK_NOT_INSTALLED_ERROR string was 'RBAC fallback not installed' but the OSS fallback actually returns 'RBAC plugin not installed'. The mismatch made every PAT creation with a roleId hit the compensating-delete branch on self-hosters with no plugin installed — including the dashboard PAT-creation flow. Aligns the constant with the canonical string. #2 internal-packages/rbac/src/fallback.ts: authenticateBearer skipped the revoked-API-key grace window (RevokedApiKey table), so a freshly-rotated env API key would 401 immediately on the new auth path. Mirrors findEnvironmentByApiKey's fallback-to-RevokedApiKey logic so the auth-cross-cutting e2e tests pass. #3 api.v1.query.ts: multi-table queries built a plain RbacResource array, which checkAuth treats as 'any element passes'. A JWT scoped to one detected table could submit a query that also reads another table it isn't scoped to. Wrap with everyResource — same AND-semantics fix as the batch trigger routes. #4 account.tokens/route.tsx: defaultRoleId could land on a custom or plan-blocked role when userRoleId wasn't in the picker's assignable set. The action's submit-revalidation would then 400 until the user manually changed the dropdown. Clamp the default to roles the picker actually renders. #5 settings.team/route.tsx: the role Select used defaultValue, so a failed set-role submit left the attempted role visible while the server kept the old one. Switch to a controlled value bound to currentRoleId.
matt-aitken
added a commit
that referenced
this pull request
May 8, 2026
#1 internal-packages/rbac/src/ability.ts (severity: 🔴 silent privilege escalation): buildJwtAbility was treating any scope starting with `admin:` as a universal wildcard. Pre-RBAC, the legacy checkAuthorization string-matched superScopes — `admin:sessions` only granted access to routes that explicitly listed it. After the JWT- ability split, the same scope was returning true for every action on every resource. Restrict the bypass to bare `admin` (no second segment); `admin:<type>` now flows through normal matching as action="admin" against resources of that type. Adds 2 regression tests in ability.test.ts. #2 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (status discard): authenticateRequestForApiBuilder hardcoded `status: 401` even though BearerAuthResult.status is `401 | 403`. A plugin returning 403 (e.g. suspended account, IP block) would silently get downgraded to 401 — semantically wrong (401 = "who are you?", 403 = "you're not allowed") and confusing for client retry logic. Plumb result.status through. #3 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (everyResource([]) vacuous truth): [].every() returns true, so everyResource([]) was passing auth for any token. Not exploitable today (Zod rejects empty bodies before auth), but the auth layer should never grant on empty input. Same defensive guard added to anyResource() for symmetry — only PERMISSIVE_ABILITY would have granted there, but the pattern shouldn't depend on the ability's choice. #4 internal-packages/rbac/src/fallback.ts (PREVIEW env regression): the fallback's authenticateBearer looked up environments by apiKey only, skipping the branch-aware resolution that findEnvironmentByApiKey does for PREVIEW envs. Self-hosters using preview/branch envs would either fail or operate against the parent env. Mirror the legacy path: read x-trigger-branch, include matching child env, and pivot the resolved env to the child (apiKey/orgMember/organization/project inherited from parent). sanitizeBranchName inlined here because internal-packages can't import webapp code; comment notes the duplication. All four flagged by Devin's PR review. Cloud plugin's buildJwtAbility gets the same #1 fix in a sibling commit on this PR's cloud branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
matt-aitken
added a commit
that referenced
this pull request
May 8, 2026
…ker tightening #1 Batch trigger AND semantics (security): `api.v[12].tasks.batch` now uses `everyResource(...)` so a JWT scoped to taskA can no longer submit a batch that also includes taskB / taskC. Added an `everyResource` helper to `apiBuilder` (Symbol-marked wrapper that flips `ability.can` to `every`). Multi-key OR semantics still apply for single-resource arrays (a run carries multiple identifiers). Updated the e2e test to assert AND behaviour. #3 Realtime stream resource (correctness): `findResource` for `realtime.v1.streams.$runId.$streamId` now selects `taskIdentifier`, `runTags`, and `realtimeStreamsVersion` — fields the auth resource builder + handler read but findResource was returning undefined for. #4 projectCreated optional chaining (crash bug): added the missing `?.` between v3Subscription and plan so a missing subscription no longer throws and aborts project creation. #5 RBAC plugin loader logging: distinguish "plugin itself missing" from "plugin found but a transitive dep failed to resolve" by inspecting the ERR_MODULE_NOT_FOUND error message for the plugin's own module specifier. The transitive-dep case now logs at error level (matches the comment's stated behaviour). Removed the orphan log line that contradicted it. #6 account.tokens picker source mismatch: the picker now sources roles from the same plan-tier-filtered list (`systemRoles().filter(available)`) as the default-role calculation. Added server-side roleId revalidation in the create action so a hand-crafted POST can't bind a PAT to an unavailable role.
matt-aitken
added a commit
that referenced
this pull request
May 8, 2026
Five real-bug fixes from CodeRabbit + Devin review of #3499: #1 personalAccessToken.server.ts: FALLBACK_NOT_INSTALLED_ERROR string was 'RBAC fallback not installed' but the OSS fallback actually returns 'RBAC plugin not installed'. The mismatch made every PAT creation with a roleId hit the compensating-delete branch on self-hosters with no plugin installed — including the dashboard PAT-creation flow. Aligns the constant with the canonical string. #2 internal-packages/rbac/src/fallback.ts: authenticateBearer skipped the revoked-API-key grace window (RevokedApiKey table), so a freshly-rotated env API key would 401 immediately on the new auth path. Mirrors findEnvironmentByApiKey's fallback-to-RevokedApiKey logic so the auth-cross-cutting e2e tests pass. #3 api.v1.query.ts: multi-table queries built a plain RbacResource array, which checkAuth treats as 'any element passes'. A JWT scoped to one detected table could submit a query that also reads another table it isn't scoped to. Wrap with everyResource — same AND-semantics fix as the batch trigger routes. #4 account.tokens/route.tsx: defaultRoleId could land on a custom or plan-blocked role when userRoleId wasn't in the picker's assignable set. The action's submit-revalidation would then 400 until the user manually changed the dropdown. Clamp the default to roles the picker actually renders. #5 settings.team/route.tsx: the role Select used defaultValue, so a failed set-role submit left the attempted role visible while the server kept the old one. Switch to a controlled value bound to currentRoleId.
matt-aitken
added a commit
that referenced
this pull request
May 8, 2026
#1 internal-packages/rbac/src/ability.ts (severity: 🔴 silent privilege escalation): buildJwtAbility was treating any scope starting with `admin:` as a universal wildcard. Pre-RBAC, the legacy checkAuthorization string-matched superScopes — `admin:sessions` only granted access to routes that explicitly listed it. After the JWT- ability split, the same scope was returning true for every action on every resource. Restrict the bypass to bare `admin` (no second segment); `admin:<type>` now flows through normal matching as action="admin" against resources of that type. Adds 2 regression tests in ability.test.ts. #2 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (status discard): authenticateRequestForApiBuilder hardcoded `status: 401` even though BearerAuthResult.status is `401 | 403`. A plugin returning 403 (e.g. suspended account, IP block) would silently get downgraded to 401 — semantically wrong (401 = "who are you?", 403 = "you're not allowed") and confusing for client retry logic. Plumb result.status through. #3 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (everyResource([]) vacuous truth): [].every() returns true, so everyResource([]) was passing auth for any token. Not exploitable today (Zod rejects empty bodies before auth), but the auth layer should never grant on empty input. Same defensive guard added to anyResource() for symmetry — only PERMISSIVE_ABILITY would have granted there, but the pattern shouldn't depend on the ability's choice. #4 internal-packages/rbac/src/fallback.ts (PREVIEW env regression): the fallback's authenticateBearer looked up environments by apiKey only, skipping the branch-aware resolution that findEnvironmentByApiKey does for PREVIEW envs. Self-hosters using preview/branch envs would either fail or operate against the parent env. Mirror the legacy path: read x-trigger-branch, include matching child env, and pivot the resolved env to the child (apiKey/orgMember/organization/project inherited from parent). sanitizeBranchName inlined here because internal-packages can't import webapp code; comment notes the duplication. All four flagged by Devin's PR review. Cloud plugin's buildJwtAbility gets the same #1 fix in a sibling commit on this PR's cloud branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
matt-aitken
added a commit
that referenced
this pull request
May 12, 2026
…ker tightening #1 Batch trigger AND semantics (security): `api.v[12].tasks.batch` now uses `everyResource(...)` so a JWT scoped to taskA can no longer submit a batch that also includes taskB / taskC. Added an `everyResource` helper to `apiBuilder` (Symbol-marked wrapper that flips `ability.can` to `every`). Multi-key OR semantics still apply for single-resource arrays (a run carries multiple identifiers). Updated the e2e test to assert AND behaviour. #3 Realtime stream resource (correctness): `findResource` for `realtime.v1.streams.$runId.$streamId` now selects `taskIdentifier`, `runTags`, and `realtimeStreamsVersion` — fields the auth resource builder + handler read but findResource was returning undefined for. #4 projectCreated optional chaining (crash bug): added the missing `?.` between v3Subscription and plan so a missing subscription no longer throws and aborts project creation. #5 RBAC plugin loader logging: distinguish "plugin itself missing" from "plugin found but a transitive dep failed to resolve" by inspecting the ERR_MODULE_NOT_FOUND error message for the plugin's own module specifier. The transitive-dep case now logs at error level (matches the comment's stated behaviour). Removed the orphan log line that contradicted it. #6 account.tokens picker source mismatch: the picker now sources roles from the same plan-tier-filtered list (`systemRoles().filter(available)`) as the default-role calculation. Added server-side roleId revalidation in the create action so a hand-crafted POST can't bind a PAT to an unavailable role.
matt-aitken
added a commit
that referenced
this pull request
May 12, 2026
Five real-bug fixes from CodeRabbit + Devin review of #3499: #1 personalAccessToken.server.ts: FALLBACK_NOT_INSTALLED_ERROR string was 'RBAC fallback not installed' but the OSS fallback actually returns 'RBAC plugin not installed'. The mismatch made every PAT creation with a roleId hit the compensating-delete branch on self-hosters with no plugin installed — including the dashboard PAT-creation flow. Aligns the constant with the canonical string. #2 internal-packages/rbac/src/fallback.ts: authenticateBearer skipped the revoked-API-key grace window (RevokedApiKey table), so a freshly-rotated env API key would 401 immediately on the new auth path. Mirrors findEnvironmentByApiKey's fallback-to-RevokedApiKey logic so the auth-cross-cutting e2e tests pass. #3 api.v1.query.ts: multi-table queries built a plain RbacResource array, which checkAuth treats as 'any element passes'. A JWT scoped to one detected table could submit a query that also reads another table it isn't scoped to. Wrap with everyResource — same AND-semantics fix as the batch trigger routes. #4 account.tokens/route.tsx: defaultRoleId could land on a custom or plan-blocked role when userRoleId wasn't in the picker's assignable set. The action's submit-revalidation would then 400 until the user manually changed the dropdown. Clamp the default to roles the picker actually renders. #5 settings.team/route.tsx: the role Select used defaultValue, so a failed set-role submit left the attempted role visible while the server kept the old one. Switch to a controlled value bound to currentRoleId.
matt-aitken
added a commit
that referenced
this pull request
May 12, 2026
#1 internal-packages/rbac/src/ability.ts (severity: 🔴 silent privilege escalation): buildJwtAbility was treating any scope starting with `admin:` as a universal wildcard. Pre-RBAC, the legacy checkAuthorization string-matched superScopes — `admin:sessions` only granted access to routes that explicitly listed it. After the JWT- ability split, the same scope was returning true for every action on every resource. Restrict the bypass to bare `admin` (no second segment); `admin:<type>` now flows through normal matching as action="admin" against resources of that type. Adds 2 regression tests in ability.test.ts. #2 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (status discard): authenticateRequestForApiBuilder hardcoded `status: 401` even though BearerAuthResult.status is `401 | 403`. A plugin returning 403 (e.g. suspended account, IP block) would silently get downgraded to 401 — semantically wrong (401 = "who are you?", 403 = "you're not allowed") and confusing for client retry logic. Plumb result.status through. #3 apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (everyResource([]) vacuous truth): [].every() returns true, so everyResource([]) was passing auth for any token. Not exploitable today (Zod rejects empty bodies before auth), but the auth layer should never grant on empty input. Same defensive guard added to anyResource() for symmetry — only PERMISSIVE_ABILITY would have granted there, but the pattern shouldn't depend on the ability's choice. #4 internal-packages/rbac/src/fallback.ts (PREVIEW env regression): the fallback's authenticateBearer looked up environments by apiKey only, skipping the branch-aware resolution that findEnvironmentByApiKey does for PREVIEW envs. Self-hosters using preview/branch envs would either fail or operate against the parent env. Mirror the legacy path: read x-trigger-branch, include matching child env, and pivot the resolved env to the child (apiKey/orgMember/organization/project inherited from parent). sanitizeBranchName inlined here because internal-packages can't import webapp code; comment notes the duplication. All four flagged by Devin's PR review. Cloud plugin's buildJwtAbility gets the same #1 fix in a sibling commit on this PR's cloud branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
matt-aitken
added a commit
that referenced
this pull request
May 23, 2026
…ication (#3708) ## Summary On a ClickHouse `Cannot parse JSON object` rejection, `RunsReplicationService` now sanitizes lone UTF-16 surrogates across the failing batch via the existing `sanitizeRows` helper and retries once. If the sanitizer found nothing or the retry also fails, the batch is dropped loudly with a counter increment, so the surrounding `#insertWithRetry` layer doesn't spin three more times on a deterministic failure. Non-parse errors propagate unchanged. Mirrors the pattern from #3659 (for `ClickhouseEventRepository`) — same root cause (lone UTF-16 surrogates in user-provided JSON), same recovery shape, **reusing the same shared helpers** (`sanitizeRows`, `isClickHouseJsonParseError`, `parseRowNumberFromError`). Fixes the customer-facing symptom from [TRI-9755](https://linear.app/triggerdotdev/issue/TRI-9755): a single row's poisoned `output` JSON used to take down the `COMPLETED_SUCCESSFULLY` UPDATE events for its 50+ batch-mates, stranding them in `EXECUTING` in ClickHouse forever and inflating "Running" counts on the Tasks page. Confirmed in production this is ongoing — ~120k stale rows accumulated in a single 5-hour burst on 2026-05-18; smaller continuous leak before and after. ## What changed `apps/webapp/app/services/runsReplicationService.server.ts`: - Imports the three helpers from `~/v3/eventRepository/sanitizeRowsOnParseError.server` (no duplication; no move). - New private `#insertWithJsonParseRecovery<T>(rows, doInsert, contextLabel, attempt)` — generic over `TaskRunInsertArray[]` and `PayloadInsertArray[]`, structurally identical to `ClickhouseEventRepository.#insertWithJsonParseRecovery`. Try → on parse error sanitize the whole batch (the `at row N` hint is logged but not used to slice — semantics under `input_format_parallel_parsing` aren't stable) → retry once → drop with loud log if sanitizer found nothing OR retry still fails. - `#insertTaskRunInserts` and `#insertPayloadInserts` extract a `doInsert` closure and hand it to the wrapper. Existing error logging, span recording, and `recordSpanError` are preserved inside the closure. - New `private _permanentlyDroppedBatches = 0` counter with a public getter, for ops dashboards and tests (matches the events-repo convention). One shared counter for both insert sites — granularity comes from the `contextLabel` (`task_runs_v2` / `raw_task_runs_payload_v1`) on every log line. `.server-changes/runs-replication-utf16-recovery.md` — release notes entry. ## Why no new tests The shared helpers already have full unit + real-ClickHouse contract coverage from #3659 (`apps/webapp/test/sanitizeRowsOnParseError.test.ts`, `apps/webapp/test/otlpUtf16Sanitization.integration.test.ts`). The new wrapper is a line-for-line structural port. Adding a parallel integration test would require synthesizing bad data that *escapes* the preemptive `detectBadJsonStrings` check in `#prepareJson` but still trips ClickHouse — non-trivial without hand-crafted fixtures and wouldn't cover any new logic. ## What this does NOT do - Doesn't touch the ~120k existing stale `EXECUTING` rows in production. That needs a reconciliation/backfill sweep (separate ticket — TRI-9755 fix #3). - Doesn't sanitize the `error` column path (`runsReplicationService.server.ts:932 const errorData = { data: run.error };`). Reactive recovery will catch it if it ever poisons a batch, but feeding it through `#prepareJson` like `output` is a cheap follow-up. ## Test plan - [x] `pnpm run typecheck --filter webapp` — clean - [ ] Post-deploy: confirm `permanentlyDroppedBatches` counter stays at zero (or near-zero) in `/stp/trigger-app-prod/ecs/replication/service-container/process-logs`, and watch for `Sanitizing batch after ClickHouse JSON parse error` warns to confirm recovery is firing on real traffic - [ ] Post-deploy: confirm the rate of new "EXECUTING-but-actually-COMPLETED" zombies in ClickHouse flattens (current rate ≈ tens-to-hundreds per hour platform-wide) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR enables the initial documentation setup for Trigger.dev, currently available at docs.trigger.dev
🚀 Setup
Simply merge in this PR and your documentation will be connected!
👩💻 Development
Install the Mintlify CLI to preview the documentation changes locally. To install, use the following command
Run the following command at the root of your documentation (where mint.json is)
😎 Publishing Changes
Changes will be deployed to production automatically after pushing to the default branch.
You can also preview changes using PRs, which generates a preview link of the docs.
Troubleshooting
mintlify installit'll re-install dependencies.mintlify clearto clear the cache.