NewOur benchmark results are out!read it

← The appendix · generated 2026-10-21

Formbricks: every bot comment, every Heal defect

An open-source survey platform.

56 PRs of its real history (2026-10-09 → 2026-10-16) replayed on a private copy, with CodeRabbit, cubic and Greptile on every PR at their default settings, Greptile's TREX on; the repo's own .coderabbit.yaml travels with the code and applies to CodeRabbit. On the Heal side, a full run before the span (t1), one midway (t2) and one after it (t3).

117 defect-claim comments on 56 PRs, 108 distinct claims once the same point from two bots is merged; the 268 walkthroughs, summaries and "no issues" posts the bots also wrote are listed but not counted. 307 Heal defects shown, 31 of them on lines a replayed PR wrote, the rest on code from before the PRs tested. 3 security defects left out.

What ran, and when

The bots ran on their vendors' hosted services, on the settings above. The vendors publish no version number, so the posting dates below are the only identity of what reviewed these PRs.

  • coderabbit: 121 comments, posted between 2026-10-20 13:15 and 2026-10-21 00:20 UTC
  • cubic: 182 comments, posted between 2026-10-20 13:16 and 2026-10-21 00:26 UTC
  • greptile: 82 comments, posted between 2026-10-20 13:24 and 2026-10-21 00:31 UTC
Security defectsSecurity defects are left out of this appendix: the full run found 3 on this product, and its maintainers have not been told about them yet. Publishing an unfixed security defect before the people who can fix it have heard of it would put their users at risk, so those rows wait for the disclosure. They are counted in every figure of the benchmark page.

The comments are shown as the bots posted them, with no verdict on whether they hold; the same-issue judge only links a comment to the Heal defect it names. A Heal defect is a failing test on the running product. Download the comments and the defects as CSV.

This product's caveats
  • Judge truth: no human graded a sample on this product, so every judge-based number is the model's alone.
  • Blame names the last PR that touched a line, not the one that created the defect, so 'several PRs' and 'pre-span' are upper bounds on what a reviewer could have caught.
  • The defect ledger is cumulative over the runs: a defect filed by a later run may sit on lines an earlier PR of the span wrote.
  • The run after the span saw the code 4 PRs past the last replayed PR (the replay was stopped early by hand); a defect whose lines those later PRs wrote falls outside the window.
  • The bots' account settings are the vendors' defaults with Greptile's TREX on; the repo's own .coderabbit.yaml travels with the code and applies to CodeRabbit.
  • Cost sums every metrics row of the workspace's runs, stopped attempts included; a run's summary card shows one segment only.
  • The first midway run (16 October) has no end record (stopped from the Studio): its 60 defects are in the ledger and its cost in the total.
385 of 385 comments
PRBotWhereClaimsThe comment
#9581
2026-10-20 13:15
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The Dockerfile comments identify `apps/web/vendor` as an installer input and state that the dependency-input `COPY` list must match the timestamp reset. A new installer step resets timestamps on manifests, patches, and vendor files to `2000-01-01 00:00`. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to 2c5c3 The Dockerfile change has no established merge-blocking risk. Architecture risk: 🔵 Low · up to 2c5c3 The change affects 1 system. **Changed systems:** `apps/web` **Architecture concerns** No architecture-level concerns identified. Review details **Systems and components** - _observed_ — apps/web (ui)
… the rest (3707 characters) was modified; 1 changed file maps to changed impact. **Before / after behavior** - _observed_ — Modified behavior in apps/web/Dockerfile: Added comments documenting that `apps/web/vendor` is required for the tarball dependency and that the dependency-input `COPY` list must remain synchronized with the later timestamp reset. - _observed_ — Modified behavior in apps/web/Dockerfile: Added an installer step that resets timestamps on all manifests, patch files, and vendor files used by dependency installation to `200001010000`, including the `apps/web/vendor` tarball inputs. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits with the valid type and scope, uses a lower-case descriptive summary without a trailing period, and includes an allowed Linear ticket reference. | | Description check | | The description includes the required ticket reference, What & why, review location, breaking-change declaration, migrations and environment details, testing coverage, open gaps, and risks. It clearly… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves `- [ ] This PR contains breaking changes` unticked. The reviewed diff changes only `apps/web/Dockerfile`: comments and a `RUN touch`/`find ... touch` step that resets timesta… | | Only The Ticket Line Carries A Magic Word | | The description has one qualifying pair: “Fixes ENG-3801” on the ticket line at the top. The other issue references, including “#9558” and “#9562”, do not have a nearby magic word. | | Coverage Accounts For The Whole Diff | | The Coverage table is populated and covers the changed Docker behavior. It records cached-install checks for the original failure, intermediate fixes, and the final timestamp reset for every install i… | Full details: Description check **Explanation** The description includes the required ticket reference, What & why, review location, breaking-change declaration, migrations and environment details, testing coverage, open gaps, and risks. It clearly explains the cached Docker install failure and the timestamp-reset fix. Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves `- [ ] This PR contains breaking changes` unticked. The reviewed diff changes only `apps/web/Dockerfile`: comments and a `RUN touch`/`find ... touch` step that resets timestamps. It does not rename or remove API fields, routes, configuration keys, database schema, or public SDK signatures. The unticked declaration matches the diff. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table is populated and covers the changed Docker behavior. It records cached-install checks for the original failure, intermediate fixes, and the final timestamp reset for every install input. Each row states a verification method and observed outcome. The `e2e` rows use the named `Validate Docker Build` rerun above the table. The `Open gaps` subsection is present and records amd64-only validation plus the arm64 limitation. The diff changes no rendered UI. Comment `@the reviewer help` to get the list of available commands.
#9581
2026-10-20 13:16
cubic
review-body
—summary
**No issues found** across 1 file View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9581
2026-10-20 13:24
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge based on the reviewed change; no actionable issues were found in apps/web/Dockerfile. Reviews (1) · Last reviewed commit: "fix(docker): reset install-input timesta..." · Reviewed by the reviewer
#9583
2026-10-20 13:25
coderabbit
issue-comment
—summary
### Walkthrough The Analyze navigation label now appears without a Beta badge. The section-label documentation states that Act remains pre-1.0. Priority: ⬇️ Low Merge Risk: 🔵 Low · up to 84e3f The navigation remains usable, but the Analyze heading may not be translated for localized users. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
… the rest (3532 characters)| | Title check | | The title follows Conventional Commits format, uses the valid feat type and nav scope, clearly describes the UI change, uses lower-case summary text, and has no trailing period. | | Description check | | The description includes the required change summary, rationale, code location, breaking-change status, migration and environment impact, test coverage, open gaps, risks, and AI attribution. It also e… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description has an unticked breaking-changes checkbox. The reviewed diff changes only `apps/web/app/(app)/workspaces/[workspaceId]/components/MainNavigation.tsx`: it replaces the Analyze label … | | Only The Ticket Line Carries A Magic Word | | The PR description starts with “No ticket” and contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with `ENG-<number>` or `#<number>`. The `#L206` URL fragment is not a GitHub issue reference, … | | Coverage Accounts For The Whole Diff | | The diff changes the Analyze section label from a Beta-wrapped label to plain "Analyze" and leaves Act beta-labeled. The description has a non-empty Coverage row for both behaviors with method `manual… | Full details: Description check **Explanation** The description includes the required change summary, rationale, code location, breaking-change status, migration and environment impact, test coverage, open gaps, risks, and AI attribution. It also explains why no ticket is included. Full details: Breaking Changes Match The Diff **Explanation** The PR description has an unticked breaking-changes checkbox. The reviewed diff changes only `apps/web/app/(app)/workspaces/[workspaceId]/components/MainNavigation.tsx`: it replaces the Analyze label with the plain string `"Analyze"` and updates related comments. It does not change an API shape, route, config key, migration, or public SDK signature. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The PR description starts with “No ticket” and contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with `ENG-&lt;number&gt;` or `#&lt;number&gt;`. The `#L206` URL fragment is not a GitHub issue reference, and `9583` appears without `#`. The `#9583` text is in PR metadata, not the description. Full details: Coverage Accounts For The Whole Diff **Explanation** The diff changes the Analyze section label from a Beta-wrapped label to plain "Analyze" and leaves Act beta-labeled. The description has a non-empty Coverage row for both behaviors with method `manual` and observed outcome `Live dev app...`. It includes before/after screenshots for the rendered UI. The Open gaps subsection is present and explains why collapsed navigation was not checked. > [!WARNING] > Some tools did not complete. Review the errors below. > > > 🔧 ESLint > > > > > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the the reviewer configuration. > > > apps/web/app/(app)/workspaces/[workspaceId]/components/MainNavigation.tsx > > > > ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. > > > > > > > > Comment `@the reviewer help` to get the list of available commands.
#9583
2026-10-20 13:26
cubic
review-body
—summary
**No issues found** across 1 file View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9583
2026-10-20 13:27
coderabbit
inline · P2
apps/web/app/(app)/workspaces/[workspaceId]/components/MainNavigation.tsx:206quality
**Localize the Analyze section label.** This heading is user-facing, but line 206 still hardcodes `"Analyze"`. Use the existing lowercase dot-nested `t()` key for this label. If the key does not exist, add it to `en-US` and run `pnpm i18n`. As per path instructions: all user-facing strings must go through `t()` with lowercase dot-nested keys, never hardcoded text. _Source: Path instructions_
#9583
2026-10-20 13:27
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9583
2026-10-20 13:33
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Low impact]** Safe to merge. Rendered navigation preserves Act's badge, Analyze's links, and collapsed behavior. Reviews (1) · Last reviewed commit: "feat(nav): remove Beta badge from the An..." · Reviewed by the reviewer
#9585
2026-10-20 13:33
coderabbit
issue-comment
—summary
### Walkthrough The changes add daily per-user limits for invitations and organization creation. Invitation actions reserve and settle usage against both organization and user budgets. Organization creation actions check the configured user limit. Invite emails now use sanitized display names, with fallbacks for missing names. Priority: ➖ Normal Merge Risk: 🔵 Low · up to 748d5 The creation limits and invite-email behavior have no established blocking defect. Correct the Cloud limit documentation and focused test issues before merging if practical. Security architecture risk: 🔵 Low · up to 748d5 The changes strengthen invitation abuse controls without expanding user permissions. Remai
… the rest (7820 characters)ning risk centers on best-effort quota enforcement: service failures can disable enforcement, while interrupted operations can temporarily consume unused capacity across a user's organizations. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _observed_ — The new invitation counter follows one authenticated actor across organizations, while each organization retains its separate shared budget. The effective user allowance follows the target organization's trusted allowance: normally 50, 500 for entitled cloud organizations, or the self-hosted configured value. **Security Findings and Attack Paths** - _inferred_ — With rate limiting operational, creating or deleting organizations no longer gives the same actor a fresh invitation counter. This narrows the existing organization-churn abuse path; it does not establish a service-wide limit against attackers controlling multiple accounts. **Trust Boundaries and Controls** - _observed_ — The creation actions use ctx.user.id rather than a client-supplied quota identity. Their existing instance and multi-organization restrictions remain after the quota check. First-account signup provisioning remains outside this budget, but requires a newly created account and has separate signup controls; that unchanged path is not evidence of a newly introduced same-user bypass. - _observed_ — The inherited limiter allows requests when disabled, when Redis is unavailable, or when quota acquisition errors. Consequently, the new budgets are best-effort abuse controls, not unconditional enforcement during infrastructure failure. The inspected base-to-head comparison does not show this failure policy being weakened. **Resilience and Maintainability Implications** - _inferred_ — Process interruption between paired acquisitions or before settlement can leave unused capacity charged, now including the actor's cross-organization budget. Existing one-shot, best-effort settlement prevents duplicate release within a receipt's process lifetime and refunds the original window key. Fixed-window expiry bounds the overcounting; durable reconciliation was not established. Pre-merge checks: 6 · 1 Failed checks: 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Only The Ticket Line Carries A Magic Word | | The supplied pull request description is truncated after the screenshot section. The repository and reviewed Git refs contain no untruncated description copy. The visible portion contains no `Fixes`, … | Provide the complete raw pull request description, including the text after the truncation, so the full body can be scanned for magic-word and issue-reference pairs. | Passed checks: 6 passed | Check name | Status | Explanation | | :---------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses the valid `fix` type and `invites` scope, stays lowercase, has no trailing period, and accurately summarizes the invite, organization-creation, and … | | Description check | | The description covers the required sections: motivation, changes, review locations, breaking-change status, migrations and environment changes, test coverage, open gaps, risks, and AI metadata. It al… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves the breaking-change checkbox unticked. The diff changes only internal server actions, rate-limit configuration, invite-email sanitization, tests, and documentation. It does n… | | Coverage Accounts For The Whole Diff | | The Coverage table accounts for all changed product behaviors: per-user and per-organization invite reservations across four call sites, rollback and settlement, per-user organization-creation limits … | Full details: Title check **Explanation** The title follows Conventional Commits format, uses the valid `fix` type and `invites` scope, stays lowercase, has no trailing period, and accurately summarizes the invite, organization-creation, and inviter-name changes. Full details: Description check **Explanation** The description covers the required sections: motivation, changes, review locations, breaking-change status, migrations and environment changes, test coverage, open gaps, risks, and AI metadata. It also explains why no ticket is referenced. Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves the breaking-change checkbox unticked. The diff changes only internal server actions, rate-limit configuration, invite-email sanitization, tests, and documentation. It does not change an API request/response shape, route, webhook payload, HTTP status mapping, environment-variable key or default, database schema, or the public SDK surface. The new rate-limit entries have defaults, and the changed helper signatures are internal modules explicitly excluded by this check. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table accounts for all changed product behaviors: per-user and per-organization invite reservations across four call sites, rollback and settlement, per-user organization-creation limits in both actions, and invite-name sanitization, fallback, and length bounds. Each unit row names relevant test files, red-on-main rows have the shared rerun command, and the mutation row names `invite-display-name.ts:32`. The manual row records Valkey and browser outcomes. The description lists Open gaps, including Cloud and clustered Redis, bare domains, and product review of the five-organization limit. It also includes screenshots for the changed rendered flows. Configuration, documentation, and test-only changes introduce no separate product behavior beyond these covered rows. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The supplied pull request description is truncated after the screenshot section. The repository and reviewed Git refs contain no untruncated description copy. The visible portion contains no `Fixes`, `Closes`, `Resolves`, or `Ref` pair with `ENG-&lt;number&gt;` or `#&lt;number&gt;`, but omitted text could contain one. **Resolution** Provide the complete raw pull request description, including the text after the truncation, so the full body can be scanned for magic-word and issue-reference pairs. > [!WARNING] > Some tools did not complete. Review the errors below. > > > 🔧 ESLint > > > > > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the the reviewer configuration. > > ESLint install timed out. The project may have too many dependencies for the sandbox. > > > > > > Comment `@the reviewer help` to get the list of available commands.
#9585
2026-10-20 13:40
coderabbit
inline · P3
apps/web/app/setup/organization/create/actions.test.ts:27tests
**Assert the handler’s successful return value.** The action-client wrapper is intentionally bypassed in this unit test. The repository uses this pattern for focused handler tests, and the real `authenticatedActionClient` result contract is tested separately. Keep the raw rejection assertion for the rate-limit path. The success test should also assert the callback output: Suggested fix ```diff - await createOrganizationAction({ ctx, parsedInput } as never); + const result = await createOrganizationAction({ ctx, parsedInput } as never); + expect(result).toEqual({ id: "org-1", name: "Acme" }); expect(mocks.applyRateLimit).toHaveBeenCalledWith(rateLimitConfigs.actions.create
… the rest (28 characters)Organization, "user-1"); ```
#9585
2026-10-20 13:40
coderabbit
inline · P2
apps/web/modules/email/invite-email.test.ts:9tests
**Move the local SMTP mock into `__mocks__`.** `sendEmail` is a local wrapper around the SMTP network. It creates a Nodemailer transport and calls `transporter.sendMail`. Keep this test from sending mail by mocking that wrapper, but place the mock in `__mocks__` as required by the repository guidance. No `@formbricks/*` helper for this local wrapper exists, so do not introduce an invented helper.
#9585
2026-10-20 13:40
coderabbit
inline · P3
apps/web/modules/organization/actions.test.ts:27tests
**Assert the successful handler return.** The mock intentionally returns the action callback, so this focused test need not exercise the action-client wrapper. However, the success case only checks mocked calls and does not assert the callback result. Capture the return value and assert the created organization. Keep the raw `TooManyRequestsError` rejection test. Suggested fix ```diff - await createOrganizationAction({ ctx, parsedInput } as never); + const result = await createOrganizationAction({ ctx, parsedInput } as never); + expect(result).toEqual({ id: "org-1", name: "Acme" }); expect(mocks.applyRateLimit).toHaveBeenCalledWith(rateLimitConfigs.actions.createOrganizatio
… the rest (17 characters)n, "user-1"); ```
#9585
2026-10-20 13:40
coderabbit
inline · P2
docs/self-hosting/configuration/rate-limiting.mdx:91docs
**Document the Cloud per-user allowance.** This row states a 50-recipient per-user limit. For an organization with the Cloud bulk-invite entitlement, `reserveInviteRateLimit` raises that user’s allowance to 500. State the exception here so the documented per-user limit matches the enforced limit.
#9585
2026-10-20 13:40
coderabbit
review-body
—summary
**Actionable comments posted: 4**
#9585
2026-10-20 13:43
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No blocking issue was identified; the focused checks support merging these abuse-prevention changes. Reviews (1) · Last reviewed commit: "fix(invites): limit invites per user and..." · Reviewed by the reviewer
#9585
2026-10-20 13:45
cubic
inline · P2
apps/web/modules/email/lib/invite-display-name.test.ts:40tests
This fixture uses a BMP character, so the test does not distinguish code-point truncation from UTF-16-unit truncation. Use an astral character such as `😀` to verify the stated guarantee. ```suggestion const longName = "😀".repeat(MAX_INVITE_DISPLAY_NAME_LENGTH + 20); ```
#9585
2026-10-20 13:45
cubic
review-body
—summary
**3 issues found** across 18 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9585
2026-10-20 13:45
cubic
inline · P2
apps/web/modules/email/lib/invite-display-name.ts:23defect-functional
A sentence-final period makes `foo@example.com.` survive this filter because `lastDotIndex` points at the final dot. Strip trailing punctuation before checking the email token so the address is removed. ```suggestion const emailCandidate = token.replace(/\.+$/u, ""); const atIndex = emailCandidate.indexOf("@"); const lastDotIndex = emailCandidate.lastIndexOf("."); return atIndex > 0 && lastDotIndex > atIndex + 1 && lastDotIndex < emailCandidate.length - 1; ```
#9585
2026-10-20 13:45
cubic
inline · P3
apps/web/modules/email/invite-email.test.ts:9tests
Move this SMTP-boundary mock into `__mocks__` to follow the repository's required mock convention.
#9542
2026-10-20 13:46
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough A shared asynchronous `copyToClipboard` utility now returns whether a clipboard write succeeded. Survey, invite, embed, code-block, personal-link, backup-code, and webhook-secret copy actions use the utility. These actions show success feedback only after a successful write and show failure feedback otherwise. Webhook copy actions also avoid updating the copied state after a failure. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to d72ec Copy actions now report clipboard failures rather than claiming success. No merge-blocking issue is established by the supplied evidence. Pre-merge checks: 7 | Check name |
… the rest (4559 characters) Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title uses the required Conventional Commits format, describes the clipboard error-handling fix, uses a lowercase summary apart from the exact UI toast text, and includes the ENG-3239 reference. | | Description check | | The description is complete and relevant. It includes the ticket reference, user-visible behavior, implementation details, affected locations, breaking-change status, migration impact, test coverage, … | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves the breaking-changes checkbox unticked. The authoritative diff changes only internal web clipboard handling and adds its unit test. It does not change an API or response shap… | | Only The Ticket Line Carries A Magic Word | | The PR description has one qualifying pair: “Fixes ENG-3239” on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-<number>` or `#<number>` reference … | | Coverage Accounts For The Whole Diff | | The description has a non-empty Coverage table with three rows. It verifies the rejected/successful clipboard outcomes through `apps/web/lib/utils/clipboard.test.ts`, names the mutation target `apps/w… | Full details: Description check **Explanation** The description is complete and relevant. It includes the ticket reference, user-visible behavior, implementation details, affected locations, breaking-change status, migration impact, test coverage, open gaps, and risks. Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves the breaking-changes checkbox unticked. The authoritative diff changes only internal web clipboard handling and adds its unit test. It does not change an API or response shape, route, webhook payload, HTTP status, default/config key, database schema, or public SDK signature. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The PR description has one qualifying pair: “Fixes ENG-3239” on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference in the description. Full details: Coverage Accounts For The Whole Diff **Explanation** The description has a non-empty Coverage table with three rows. It verifies the rejected/successful clipboard outcomes through `apps/web/lib/utils/clipboard.test.ts`, names the mutation target `apps/web/lib/utils/clipboard.ts:7-12`, and records observed results. It also records manual review of the remaining changed copy handlers with an outcome. The diff changes clipboard feedback in those handlers and adds the shared helper; the coverage rows account for these behaviors. The description includes a Rerun command, an Open gaps subsection with reasons, and before/after UI screenshots for the rendered toast change. > [!WARNING] > Some tools did not complete. Review the errors below. > > > 🔧 ESLint > > > > > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the the reviewer configuration. > > > apps/web/app/(app)/workspaces/[workspaceId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/email-tab.tsx > > > > ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. > > > > > > > apps/web/app/(app)/workspaces/[workspaceId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/pretty-url-tab.tsx > > > > ESLint skipped: the matched ESLint configuration already failed (missing-dependency). > > > > > > > apps/web/app/(app)/workspaces/[workspaceId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/website-embed-tab.tsx > > > > ESLint skipped: the matched ESLint configuration already failed (missing-dependency). > > > > > > + 10 others > > > > Comment `@the reviewer help` to get the list of available commands.
#9542
2026-10-20 13:52
cubic
review-body
—summary
**No issues found** across 13 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9542
2026-10-20 13:54
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No blocking issues were identified; the change is safe to merge based on the reviewed changes and focused browser checks. Reviews (1) · Last reviewed commit: "fix(web): report a rejected clipboard wr..." · Reviewed by the reviewer
#9590
2026-10-20 13:56
coderabbit
issue-comment
—summary
### Walkthrough The pull request deletes `.github/CODEOWNERS` and the workflow that validated its ownership rule. The review guide now instructs PR authors to select a reviewer and request a review through GitHub, which sends the reviewer a notification. Priority: ⬇️ Low Merge Risk: 🔵 Low · up to 48a61 The documentation may mislead authors until the organization-level assignment setting is disabled or the prerequisite is stated. Architecture risk: 🔵 Low · up to 48a61 The change affects 1 system. **Changed systems:** `docs` **Architecture concerns** No architecture-level concerns identified. Review details **Systems and components** - _observed_ — docs (service) was modified; 1 ch
… the rest (4334 characters)anged file maps to changed impact. **Before / after behavior** - _observed_ — Modified behavior in docs/development/standards/qa/code-reviews.mdx: Reviewer selection is now performed by the PR author, and GitHub review requests notify reviewers; the previous automatic assignment and notification steps were removed. - _observed_ — Modified behavior in .github/CODEOWNERS: The `.github/CODEOWNERS` file, including its sole `* @formbricks/engineering` rule and associated review-assignment documentation, was deleted. - _observed_ — Modified behavior in .github/workflows/codeowners-validation.yml: The CODEOWNERS validation workflow was removed, including its pull-request, weekly scheduled, and manual triggers; runner hardening and checkout; enforcement of a single team-wide ownership rule; fork-specific notices and reduced checks; and validator checks for syntax, duplicate patterns, unmatched files, owners, team-only ownership, and shadowing using the configured token. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title uses the required Conventional Commits format and accurately summarizes removal of CODEOWNERS and automatic reviewer assignment. | | Description check | | The description follows the required template and documents the purpose, affected files, breaking-change status, migrations, testing, coverage, gaps, risks, and AI details. | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description has an unticked breaking-change checkbox. The diff only deletes `.github/CODEOWNERS` and its validation workflow, and updates reviewer-assignment documentation. It does not change a… | | Only The Ticket Line Carries A Magic Word | | The description has one magic-word pair: `Fixes ENG-3811` on the top ticket line. No other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears with an `ENG-<number>` or `#<number>` reference elsewher… | | Coverage Accounts For The Whole Diff | | The diff only deletes repository process files and updates reviewer-assignment documentation. The Coverage table is non-empty and has manual verification rows with methods and observed outcomes for re… | Full details: Breaking Changes Match The Diff **Explanation** The PR description has an unticked breaking-change checkbox. The diff only deletes `.github/CODEOWNERS` and its validation workflow, and updates reviewer-assignment documentation. It does not change an API shape, endpoint, webhook, HTTP status, default/config key, database schema, or public SDK signature covered by this check. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The description has one magic-word pair: `Fixes ENG-3811` on the top ticket line. No other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears with an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference elsewhere in the description. Full details: Coverage Accounts For The Whole Diff **Explanation** The diff only deletes repository process files and updates reviewer-assignment documentation. The Coverage table is non-empty and has manual verification rows with methods and observed outcomes for removed-file references and manual reviewer assignment. The Open gaps subsection is present and records the external GitHub organization setting and other repositories. The description also states that the change affects repository process files and documentation, not product behavior. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 1 - Waived: $0.25 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9590
2026-10-20 13:58
coderabbit
inline · P2
docs/development/standards/qa/code-reviews.mdx:28docs
**Qualify the automatic-assignment statement.** Deleting repository `CODEOWNERS` does not disable GitHub organization-level automatic reviewer assignment. Disable that setting before this sentence is true, or state the prerequisite in the documentation.
#9590
2026-10-20 13:58
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9590
2026-10-20 13:59
cubic
review-body
—summary
**No issues found** across 3 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9590
2026-10-20 14:01
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge within the repository scope; no blocking issues were found. Reviews (1) · Last reviewed commit: "chore: remove CODEOWNERS and automatic P..." · Reviewed by the reviewer
#9586
2026-10-20 14:02
coderabbit
issue-comment
—summary
### Walkthrough The application error boundaries now use a shared helper to log or report errors based on environment and error properties. Survey routes now use a shared error page with localized messaging, Sentry reporting, and a retry callback. Priority: ⬇️ Low Merge Risk: 🟡 Moderate · up to 19f99 Survey respondents on /s, /p, and /c links now see a dedicated error page. However, its Try again button does not work, so respondents cannot recover without reloading. Rename the retry prop to the one Next.js actually supplies before merging. Security architecture risk: 🔵 Low · up to 19f99 The change retains existing reporting and authorization controls. However, the shared survey recov
… the rest (7346 characters)ery action appears incompatible with the route framework’s callback contract, potentially leaving respondents without working in-page recovery after a crash. No new privilege or data-access path was identified in the inspected flows. **Retained concerns** - **Low · reliability · inferred:** The shared respondent boundary expects and invokes retry without a prop adapter in any of its three route exports. Available framework-contract evidence does not establish that this callback is supplied. Clicking Try again therefore appears to throw instead of recovering the failed segment, making the same in-page recovery failure common to all three public survey boundaries. Security review details **Security Blast Radius** - _inferred_ — Respondent-facing exposure spans public survey, slug, and contact-link error handling. The shared reporting policy also affects application and global failures, but the inspected change does not grant respondents additional server authority or data-store access. **Trust Boundaries and Controls** - _observed_ — The inspected survey entry flow validates survey and single-use credentials; the contact flow verifies its token and checks existing response state before rendering. The new error component contains no alternate authorization or submission path. - _observed_ — Server reporting ownership predates this PR. Its handler skips expected errors, conditionally attributes non-public Node requests to a signed-in user, and captures remaining request errors. The PR leaves this handler unchanged while removing duplicate digest-bearing reports from client boundaries. Pre-merge checks: 6 · 1 Failed checks: 1 warning | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Coverage Accounts For The Whole Diff | | The description includes a non-empty Coverage table, verification methods, outcomes, a mutation location, and an Open gaps entry for the untested `/p/[slug]` route. However, the diff adds rendered UI … | Add a screenshot or screencast of the new respondent error page to the PR description. Keep the existing `/p/[slug]` item under Open gaps, or add a verification row if that route is tested. | Passed checks: 6 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses the valid `fix` type and scoped summary, describes the route error-boundary change, uses lower case without a trailing period, and includes the Line… | | Description check | | The description includes the required ticket reference, change summary, review locations, breaking-change declaration, migration and environment status, test command, coverage table, open gaps, and ri… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR declares no breaking changes with an unticked checkbox. The diff adds route error boundaries and translations, changes internal error-reporting behavior, and adds an internal helper and tests. … | | Only The Ticket Line Carries A Magic Word | | The description contains one qualifying pair: “Fixes ENG-3171” on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-<number>` or `#<number>` referenc… | Full details: Title check **Explanation** The title follows Conventional Commits format, uses the valid `fix` type and scoped summary, describes the route error-boundary change, uses lower case without a trailing period, and includes the Linear ticket reference. Full details: Description check **Explanation** The description includes the required ticket reference, change summary, review locations, breaking-change declaration, migration and environment status, test command, coverage table, open gaps, and risks. It clearly matches the pull request changes. Full details: Breaking Changes Match The Diff **Explanation** The PR declares no breaking changes with an unticked checkbox. The diff adds route error boundaries and translations, changes internal error-reporting behavior, and adds an internal helper and tests. It does not remove or rename an endpoint or route, change an API shape, HTTP status, environment/config key, database schema, or the public SDK surface. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The description contains one qualifying pair: “Fixes ENG-3171” on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Coverage Accounts For The Whole Diff **Explanation** The description includes a non-empty Coverage table, verification methods, outcomes, a mutation location, and an Open gaps entry for the untested `/p/[slug]` route. However, the diff adds rendered UI in `apps/web/modules/survey/link/error.tsx` and three route error pages. The description provides only text from manual checks and no screenshot or screencast attachment. **Resolution** Add a screenshot or screencast of the new respondent error page to the PR description. Keep the existing `/p/[slug]` item under Open gaps, or add a verification row if that route is tested. > [!WARNING] > Some tools did not complete. Review the errors below. > > > 🔧 ESLint > > > > > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the the reviewer configuration. > > > apps/web/app/c/[jwt]/error.tsx > > > > ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. > > > > > > > apps/web/app/error.tsx > > > > ESLint skipped: the matched ESLint configuration already failed (missing-dependency). > > > > > > > apps/web/app/global-error.tsx > > > > ESLint skipped: the matched ESLint configuration already failed (missing-dependency). > > > > > > + 5 others > > > > ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 9 - Waived: $2.25 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9586
2026-10-20 14:05
coderabbit
inline · P1
apps/web/modules/survey/link/error.tsx:11defect-functional
**Use the retry prop supplied by Next.js.** The three route `error.tsx` files export `LinkSurveyError` directly. Next.js supplies `reset` and `unstable_retry`, not `retry`. When a respondent clicks **Try again**, Line 27 calls `undefined` and cannot recover. Use `unstable_retry` here so a server-side failure can also re-fetch the route segment. (nextjs.org) Proposed fix ```diff interface LinkSurveyErrorProps { error: Error & { digest?: string }; - retry: () => void; + unstable_retry: () => void; } -export const LinkSurveyError = ({ error, retry }: Readonly<LinkSurveyErrorProps>) => { +export const LinkSurveyError = ({ error, unstable_retry }: Readonly<LinkSurveyErrorProps>) => {
… the rest (134 characters)... - <Button className="mt-2" onClick={() => retry()}> + <Button className="mt-2" onClick={() => unstable_retry()}> ```
#9586
2026-10-20 14:05
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9586
2026-10-20 14:10
cubic
review-body
—summary
**No issues found** across 24 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9586
2026-10-20 14:18
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** The reviewed changes are safe to merge; no blocking or non-blocking issues were found. Reviews (1) · Last reviewed commit: "fix(survey-link): give link survey route..." · Reviewed by the reviewer
#9589
2026-10-20 14:19
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The synchronization script now creates Linear issues for open Dependabot alerts in Triage with the `vulnerability` label. It also reopens matching completed or canceled issues in Triage. The configured state and label constants and related comments were updated. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to 7d851 The triage routing change has no established blocking issue and is mergeable after normal checks. Security architecture risk: 🔵 Low · up to 7d851 The changes preserve existing credentials, permissions, and issue ownership. The remaining uncertainty is whether reopened tickets, which retain their
… the rest (4257 characters)old labels, reliably trigger the intended security reassessment. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _observed_ — The script's creation and lookup operations remain scoped to the fixed Engineering team, and state updates use the matched issue ID. This establishes the script's target scope, not the maximum permissions of the externally provisioned Linear credential. **Trust Boundaries and Controls** - _observed_ — External alert metadata is written as issue content through GraphQL variables. Team, workflow-state, and label selectors are script-controlled rather than derived from advisory text. The newly intended assessment handoff occurs after the issue reaches Linear. **Resilience and Maintainability Implications** - _observed_ — Dry-run branches avoid create, reopen, and close mutations. Workflow serialization limits overlapping scheduled and manual runs, but does not establish protection against independent external writers. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows the Conventional Commits format, uses the allowed `fix` type, stays lowercase, has no trailing period, and accurately describes the Dependabot-to-Linear state and label change. | | Description check | | The description covers the change, rationale, affected code paths, breaking-change status, migrations, testing coverage, open gaps, and risks. It also explains why no ticket reference is included. | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves `- [ ] This PR contains breaking changes` unticked. The authoritative diff changes only `scripts/dependabot-to-linear.mjs`: it changes Linear issue state and label IDs for in… | | Only The Ticket Line Carries A Magic Word | | The PR description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with an `ENG-<number>` or `#<number>`. The top line says `No ticket: process change requested directly`. A scan of the sup… | | Coverage Accounts For The Whole Diff | | The description has a non-empty Coverage table and an Open gaps subsection. The diff changes three behaviors: new issues use the `vulnerability` label, new issues use `Triage`, and reopened issues use… | Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves `- [ ] This PR contains breaking changes` unticked. The authoritative diff changes only `scripts/dependabot-to-linear.mjs`: it changes Linear issue state and label IDs for internal Dependabot synchronization, including the reopen path. It does not change an API or webhook shape, endpoint, HTTP status, environment variable, config key, database schema, or public SDK signature. Therefore, the diff contains no breaking change under this check. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The PR description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with an `ENG-&lt;number&gt;` or `#&lt;number&gt;`. The top line says `No ticket: process change requested directly`. A scan of the supplied description found no magic-word/reference pair. Full details: Coverage Accounts For The Whole Diff **Explanation** The description has a non-empty Coverage table and an Open gaps subsection. The diff changes three behaviors: new issues use the `vulnerability` label, new issues use `Triage`, and reopened issues use `Triage`. Each behavior has a `manual` row with a verification method and observed outcome. The unexecuted create and reopen API paths are also listed under Open gaps with a reason. The diff does not change rendered UI. Comment `@the reviewer help` to get the list of available commands.
#9589
2026-10-20 14:20
cubic
review-body
—summary
**No issues found** across 1 file View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9589
2026-10-20 14:23
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge; no blocking or non-blocking issues were found. Reviews (1) · Last reviewed commit: "fix: create Dependabot Linear tickets as..." · Reviewed by the reviewer
#9595
2026-10-20 14:25
cubic
issue-comment
—summary
This PR is large and would use a significant portion of your monthly review quota. Comment `@the reviewer-dev-ai review this` to confirm that you want the reviewer to review it.
#9595
2026-10-20 14:25
coderabbit
issue-comment
—summary
### Walkthrough The pull request adds a Qualtrics QSF import flow that reads and validates exports, uses AI to map questions, assembles a draft survey, and streams progress and an import report to the review interface. It also adds API body and concurrency limits, shared AI and streaming utilities, reusable draft-creation components, and email rich-text sanitization. Tests, fixtures, localization, evaluation tooling, and documentation support these changes. Priority: ➖ Normal Merge Risk: 🔵 Low · up to 70bf4 The Qualtrics import is mergeable with small follow-ups. In some cases, imported plain-text labels can render as formatting rather than literal text. Long draft previews may also fli
… the rest (9371 characters)cker while scrolling. Neither issue exposes users to script execution or loses data. Security architecture risk: 🟡 Moderate · up to 70bf4 The upload flow can turn already-checked text back into markup. Workspace permissions, explicit draft approval, and later sanitization limit exposure, but a retained content-safety issue and incomplete deployment validation leave meaningful risk. **Retained concerns** - **High · security · observed:** The new importer removes unsupported piped-text expressions after sanitizing localized fields. That deletion can reconstruct markup without reapplying the import’s rich/plain safety policy before returning the draft. This is the boundary failure underlying the retained reportable XSS finding. Later render-time sanitizers are material counterevidence against unrestricted script execution, but do not preserve the importer’s original content contract. Security review details **Security Blast Radius** - _inferred_ — A malicious QSF author can supply the content, but processing requires a session with workspace write access, and persistence requires a separate authorized create action. The supported attack surface is the imported survey and its later consumers, potentially including respondents if distributed. This pass establishes neither cross-tenant access nor server, credential, or infrastructure compromise. **Security Findings and Attack Paths** - _observed_ — The canonical security assessment retains a reportable XSS finding at unsupported-pipe removal. Exact source confirms the relevant ordering: sanitize first, delete pipe expressions during assembly, then return the resulting field without another import sanitization pass. Deleting an expression between otherwise inert fragments can form markup. Survey render-time sanitization and email sanitization are important counterevidence; this pass does not independently establish arbitrary script execution in a browser or mail client. **Trust Boundaries and Controls** - _observed_ — Workspace authorization precedes AI preparation, and canonical workspace and organization identifiers flow into processing. The AI service checks entitlement, enablement, and configuration. Final creation independently checks workspace write access and replaces the submitted workspace identifier with the authorized one before writing; ownership is derived from the authenticated principal. **Resilience and Maintainability Implications** - _observed_ — Failure containment combines pre-read concurrency admission, bounded input, a 120-second processing deadline, bounded AI attempts, and cancellation propagation. Import itself performs no survey write, so interruption does not require undoing a partially imported survey. Eventual provider-side cancellation and capacity across multiple processes remain deployment-dependent rather than proven guarantees. **Hardening Proposals** - _proposed_ — Enforce the intended rich/plain content policy after all piped-text transformations, while preserving only assembler-authorized recall references. Make the final localized field—not merely its pre-transformation input—the content-safety boundary. Pre-merge checks: 4 · 2 · 1 Failed checks: 2 warnings · 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Title check | | The title follows the Conventional Commits structure and accurately describes the Qualtrics import change, but the summary is not fully lowercase because it uses “Qualtrics.” | Change the title to use a lowercase summary, for example: `feat(surveys): import qualtrics surveys from .qsf files`. | | Coverage Accounts For The Whole Diff | | The diff changes internal NDJSON streaming, request-abort handling, the QSF import route, and AI-service failure logging. The Coverage table names v3 API, surveys, email, custom CSS, and general E2E t… | Add Coverage rows with rerunnable tests and observed outcomes for internal streaming, abort/deadline handling, the QSF import route, and AI-service error classification. Alternatively, list each unverified area under Open gaps with a reason… | | Only The Ticket Line Carries A Magic Word | | The full pull request description is unavailable. The supplied body is explicitly truncated, so the complete description cannot be scanned. The visible portion contains the permitted top-line pair `Re… | Provide the complete raw pull request description, including the truncated ending, and scan every occurrence of `Fixes`, `Closes`, `Resolves`, or `Ref` followed by `ENG-` or `#`. Rewrite any offending prose reference as `ENG… | Passed checks: 4 passed | Check name | Status | Explanation | | :----------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Description check | | The description is complete and relevant. It includes the ticket reference, change summary, review locations, breaking-change details, migration notes, test coverage, open gaps, risks, and AI metadata… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR ticks the breaking-change checkbox and documents the change. The diff adds `V3_REQUEST_MAX_DEPTH = 256` in `apps/web/app/api/v3/lib/array-budget.ts` and returns HTTP 400 with a `Too deep` inval… | Full details: Description check **Explanation** The description is complete and relevant. It includes the ticket reference, change summary, review locations, breaking-change details, migration notes, test coverage, open gaps, risks, and AI metadata. Full details: Breaking Changes Match The Diff **Explanation** The PR ticks the breaking-change checkbox and documents the change. The diff adds `V3_REQUEST_MAX_DEPTH = 256` in `apps/web/app/api/v3/lib/array-budget.ts` and returns HTTP 400 with a `Too deep` invalid parameter through `apps/web/app/api/v3/lib/api-wrapper.ts`; the shared check also affects MCP requests. The description table identifies this v3/MCP behavior. Other changes are additive, internal, or optional, and no SDK surface, endpoint, migration, required configuration, or existing response shape is removed. Full details: Coverage Accounts For The Whole Diff **Explanation** The diff changes internal NDJSON streaming, request-abort handling, the QSF import route, and AI-service failure logging. The Coverage table names v3 API, surveys, email, custom CSS, and general E2E tests, but does not name `app/api/internal` or `apps/web/lib/ai`. The Open gaps section does not list these areas. The description includes screenshots, so the rendered-UI condition passes. **Resolution** Add Coverage rows with rerunnable tests and observed outcomes for internal streaming, abort/deadline handling, the QSF import route, and AI-service error classification. Alternatively, list each unverified area under Open gaps with a reason. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The full pull request description is unavailable. The supplied body is explicitly truncated, so the complete description cannot be scanned. The visible portion contains the permitted top-line pair `Ref ENG-3656`, but omitted text may contain additional magic-word pairs. **Resolution** Provide the complete raw pull request description, including the truncated ending, and scan every occurrence of `Fixes`, `Closes`, `Resolves`, or `Ref` followed by `ENG-&lt;number&gt;` or `#&lt;number&gt;`. Rewrite any offending prose reference as `ENG-…` or remove the magic word. > [!WARNING] > Some tools did not complete. Review the errors below. > > > 🔧 ESLint > > > > > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the the reviewer configuration. > > > apps/web/app/api/internal/lib/ai-stream-errors.test.ts > > > > ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. > > > > > > > apps/web/app/api/internal/lib/ai-stream-errors.ts > > > > ESLint skipped: the matched ESLint configuration already failed (missing-dependency). > > > > > > > apps/web/app/api/internal/lib/ndjson-stream.test.ts > > > > ESLint skipped: the matched ESLint configuration already failed (missing-dependency). > > > > > > + 137 others > > > > Comment `@the reviewer help` to get the list of available commands.
#9595
2026-10-20 14:33
coderabbit
inline · P2
apps/web/modules/survey/components/template-list/components/ai-draft-preview.tsx:56defect-functional
**Turn off the row fade-in when the list is windowed.** `AiDraftRow` always applies `animate-fadeIn`. In the non-virtualized list, a row mounts once, so the fade plays once when the question arrives. In the windowed list (lines 200-211), `slice(rowWindow.first, rowWindow.last)` unmounts rows that leave the window. It mounts them again when they come back. Each scroll that moves the window therefore replays the fade on every row that enters, and those rows flash in. This affects every review of a draft with more than 50 questions, for example a 150-question import. Apply the fade only to rows that the windowed list does not position. One option is a flag set by the windowed list. Another op
… the rest (1171 characters)tion is to omit the class when `position` is set. Proposed fix ```diff --- "a/apps/web/modules/survey/components/template-list/components/ai-draft-preview.tsx" +++ "b/apps/web/modules/survey/components/template-list/components/ai-draft-preview.tsx" @@ -52,9 +52,9 @@ : undefined; return ( <li - className={cn("flex animate-fadeIn items-start gap-3 px-4 py-3", className)} + className={cn("flex items-start gap-3 px-4 py-3", !position && "animate-fadeIn", className)} style={{ minHeight: ROW_HEIGHT_PX, ...style }} aria-posinset={position ? position.index + 1 : undefined} aria-setsize={position?.total}> <span className="mt-0.5 flex shrink-0 items-center text-slate-600">{icon}</span> ``` 📝 Committable suggestion > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion className={cn("flex items-start gap-3 px-4 py-3", !position && "animate-fadeIn", className)} ```
#9595
2026-10-20 14:33
coderabbit
inline · P2
apps/web/modules/survey/import/qsf/piped-text.ts:103defect-security
**XSS** **Reachability:** External **Exploitability:** Moderate **CWE:** CWE-79 — Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') **Re-sanitize text after unresolved pipe removal.** `sanitizeText` protects plain text before assembly, but `replacePipedText` can remove a pipe and create an HTML element afterward. For example: ```text <${lm://x}img src=x onerror=alert(1)> ``` becomes an `<img>` element in a plain label. The renderers sanitize HTML before insertion, so this does not establish XSS. It still breaks the import contract that plain text remains literal and can make imported labels or headlines render as allowed HTML. Re-run `sanitizeText` wit
… the rest (139 characters)h the original format after replacement, or resolve pipes before the initial sanitization while preserving the existing reporting behavior.
#9595
2026-10-20 14:33
coderabbit
inline · P2
apps/web/scripts/qsf-import-eval.test.ts:60tests
**Resolve the `afterEach` promise when `server` is undefined.** `new Promise((resolve) => server?.close(resolve))` resolves only through the `close` callback. If `server` is `undefined`, optional chaining skips the call and `resolve` never runs. This happens when `startRefusingProvider()` rejects before the assignment on Line 67. The hook then waits until the hook timeout expires. That timeout error hides the original failure. 🐛 Proposed fix ```diff --- "a/apps/web/scripts/qsf-import-eval.test.ts" +++ "b/apps/web/scripts/qsf-import-eval.test.ts" @@ -54,10 +54,11 @@ describe("pnpm qsf:eval", () => { let server: Server | undefined; afterEach(async () => { - await new Promise((r
… the rest (739 characters)esolve) => server?.close(resolve)); + const current = server; server = undefined; + if (current) await new Promise((resolve) => current.close(resolve)); }); test( "loads the app as ES modules, ESM-only packages included, and reaches the provider", ``` 📝 Committable suggestion > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion afterEach(async () => { const current = server; server = undefined; if (current) await new Promise((resolve) => current.close(resolve)); }); ```
#9595
2026-10-20 14:33
coderabbit
inline · P2
apps/web/scripts/qsf-import-eval.ts:111defect-data
**Deduplicate `skipped` and `pages` when `mergeResponses` merges answers.** `questions` is keyed by `ref`, so the last answer wins. `skipped` and `pages` are appended from every response. Retries break this in two ways: - If a question is skipped on the first attempt and answered on the retry, the recorded plan lists that ref in both `questions` and `skipped`. - If a skip is repeated across attempts, it is written twice. For a chunked fixture such as `large-150`, each call can return the same pages. The recorded plan then repeats page entries once per call. `--record` rewrites the plans that CI replays through `planForRefs`, so these duplicates become test input. Key all three collections
… the rest (2939 characters) the same way, and let a later answer replace an earlier one. ♻️ Proposed fix ```diff --- "a/apps/web/scripts/qsf-import-eval.ts" +++ "b/apps/web/scripts/qsf-import-eval.ts" @@ -97,18 +97,28 @@ } /** One plan from every answer of a run, a question's last answer winning (the retry's). */ const mergeResponses = (responses: unknown[]): TRecordedPlan => { const questions = new Map<unknown, { ref?: unknown }>(); - const plan: TRecordedPlan = { questions: [], skipped: [], pages: [] }; + const skipped = new Map<unknown, { ref?: unknown }>(); + const pages = new Map<unknown, { id?: unknown }>(); for (const response of responses) { - const object = response as Partial<TRecordedPlan>; - plan.pages.push(...(object.pages ?? [])); - plan.skipped.push(...(object.skipped ?? [])); - for (const question of object.questions ?? []) questions.set(question.ref, question); + const object = response as { + questions?: { ref?: unknown }[]; + skipped?: { ref?: unknown }[]; + pages?: { id?: unknown }[]; + }; + for (const page of object.pages ?? []) pages.set(page.id, page); + for (const question of object.questions ?? []) { + questions.set(question.ref, question); + skipped.delete(question.ref); + } + for (const skip of object.skipped ?? []) { + skipped.set(skip.ref, skip); + questions.delete(skip.ref); + } } - plan.questions = [...questions.values()]; - return plan; + return { questions: [...questions.values()], skipped: [...skipped.values()], pages: [...pages.values()] }; }; /** One run of one survey; its merged plan written into `recordDir` when that is set. */ const runOnce = async (name: string, qsf: Record<string, unknown>, recordDir: string | null) => { ``` 📝 Committable suggestion > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion const mergeResponses = (responses: unknown[]): TRecordedPlan => { const questions = new Map<unknown, { ref?: unknown }>(); const skipped = new Map<unknown, { ref?: unknown }>(); const pages = new Map<unknown, { id?: unknown }>(); for (const response of responses) { const object = response as { questions?: { ref?: unknown }[]; skipped?: { ref?: unknown }[]; pages?: { id?: unknown }[]; }; for (const page of object.pages ?? []) pages.set(page.id, page); for (const question of object.questions ?? []) { questions.set(question.ref, question); skipped.delete(question.ref); } for (const skip of object.skipped ?? []) { skipped.set(skip.ref, skip); questions.delete(skip.ref); } } return { questions: [...questions.values()], skipped: [...skipped.values()], pages: [...pages.values()] }; }; ```
#9595
2026-10-20 14:33
coderabbit
review-body
—summary
**Actionable comments posted: 4**
#9595
2026-10-20 14:42
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The changes reach the Qualtrics import flow and shared survey APIs\. Do not merge until question-pipe conversion preserves the intended content or warns about unsupported pipe kinds; the randomization and report-count defects are non-blocking. Findings 1. **Piped text shows wrong values** 2. **An ordinary choice never moves** 3. **Skip rules disappear from reports** Reviews (1) · Last reviewed commit: "feat(surveys): import Qualtrics surveys ..." · Reviewed by the reviewer
#9595
2026-10-20 14:42
greptile
inline · P1
apps/web/modules/survey/import/qsf/piped-text.ts:89defect-functional
**Piped text shows wrong values** `replacePipedText` discards everything after the question ID in a `q://` pipe. For example, `${q://QID1/QuestionText}` in a later question becomes answer recall, so respondents see their answer instead of the referenced question text. Execution confirmed that this draft passes validation with `removed: 0` and no report issues. Fix this before merging: only convert pipe kinds that match Formbricks answer recall, and preserve the semantics of other kinds or report them as unsupported. Artifacts **Evidence from the check** - The executed isolated test exercises real assembly, reporting, validation, and survey recall with recorded planning, reproducing F1 w
… the rest (1064 characters)ithout live services. **Evidence from the check** - The executed configuration resolves web import modules and survey runtime helpers to their real source files, keeping the check narrowly scoped. **Evidence from the check** - The executed script sources the required environment and runs both conditions with command, working directory, and exit-code headers, making the captures reproducible. **Command output from the check** - The control run passed literal source question text through the same stack and preserved it with and without an answer, establishing the expected semantic result rather than Git-base behavior. **Command output from the check** - The candidate run converted the pipe to answer recall, resolved it to Search engine, and emitted no report issues, confirming silent semantic corruption. **Command output from the check** - The command capture records the converter, assembly, report, validation gate, runtime recall source, and clean tracked diff, tying the reproduction to unchanged source. Ran code and verified through TREX
#9595
2026-10-20 14:42
greptile
inline · P2
apps/web/modules/survey/import/qsf/assemble.ts:144defect-functional
**An ordinary choice never moves** `choiceShuffle` returns `exceptLast` whenever a randomized question has an Other or None choice. But `getShuffledChoicesIds` already removes both special choices before shuffling. Its `exceptLast` mode therefore also pins the last ordinary choice, changing the intended answer order and introducing positional bias. This is a non-blocking randomization defect. Return `all` for randomized questions so every ordinary choice can move while the special choices remain last. Artifacts **Evidence from the check** - Authored focused test executes real assembly and runtime shuffle implementations with deterministic entropy, reproducing the pinned ordinary choice.
… the rest (751 characters) **Evidence from the check** - Focused Vitest configuration resolves the web importer and survey runtime source in one process, allowing the cross-stack behavior to execute without service mocks. **Evidence from the check** - Executable capture script sources the supplied environment and runs both comparison conditions, recording actual command output and exit codes. **Command output from the check** - Executed the all control across four supported cases; Gamma moved and Other/None stayed last, demonstrating that exceptLast is unnecessary. **Command output from the check** - Executed the unmodified assembly output across the same four cases; exceptLast pinned Gamma before Other/None, confirming F2. Ran code and verified through TREX
#9595
2026-10-20 14:42
greptile
inline · P2
apps/web/modules/survey/import/qsf/read-qsf.ts:950defect-functional
**Skip rules disappear from reports** `readQuestionLogic` drops every skip rule after the first 20 without recording the loss. A valid question with 21 choice-based skip rules imports successfully, but its report accounts for only 20 because `reportQuestionLogic` uses the shortened array's length. No other report warning discloses the extra omission. This non-blocking reporting gap prevents authors from identifying all routing rules they need to rebuild. Keep a count of the omitted rules and report them, even if their details are not sent to AI. Artifacts **Evidence from the check** - The executed test constructs matched valid QSF inputs and runs the real reader, checked recorded-plan f
… the rest (1066 characters)low, assembly, final gate, and report builder to expose the missing rule count. **Evidence from the check** - The configuration runs only the authored F3 test with the repository aliases and unit setup, leaving tracked source files unchanged. **Command output from the check** - Executed the 20-rule control and captured the command, working directory, exit code, and complete observed issue arrays, showing all 20 rules accounted for. **Command output from the check** - Executed the same flow with 21 rules and captured the complete report, showing only 20 rules accounted for and no warning about the additional omission. **Command output from the check** - Captured the reader cap, report counting code, pipeline issue-collection locations, and Git state, confirming the relevant locations and absence of tracked edits. **Command output from the check** - Captured the initial failed test invocation before the environment was successfully sourced under Bash; subsequent control and reproduction runs both succeeded. Ran code and verified through TREX
#9599
2026-10-20 14:43
cubic
review-body
—summary
**No issues found** across 1 file View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9599
2026-10-20 14:44
coderabbit
issue-comment
—summary
### Walkthrough The migration guide no longer describes the v6.1 MCP response scope migrations, client scope handling, consent behavior, or manual handling for clients that skip consent. Priority: ⬇️ Low Merge Risk: 🔵 Low · up to 31cf6 Self-hosters with affected MCP clients may encounter invalid_scope errors without the removed upgrade guidance. Restore that exception in a stable guide before merging. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------
… the rest (2814 characters)----------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows the Conventional Commits format, uses the valid `docs` type, clearly describes the documentation removal, uses lower-case wording, and has no trailing period. | | Description check | | The description covers the required sections, explains the documentation-only change, records breaking changes and migrations, describes manual coverage, and lists open gaps and risks. | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The checkbox is unticked. The reviewed diff changes only `docs/self-hosting/advanced/migration.mdx` and removes a documentation section. It does not change an API shape, endpoint, webhook, status code… | | Only The Ticket Line Carries A Magic Word | | PASS: The PR description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` followed by `ENG-<number>` or `#<number>`. The only ticket-like text is `No ticket` and the closing note mentions `pull req… | | Coverage Accounts For The Whole Diff | | PASS — The authoritative diff changes only `docs/self-hosting/advanced/migration.mdx` and deletes the documented `v6.1` section. The description states that this is documentation-only and introduces n… | Full details: Breaking Changes Match The Diff **Explanation** The checkbox is unticked. The reviewed diff changes only `docs/self-hosting/advanced/migration.mdx` and removes a documentation section. It does not change an API shape, endpoint, webhook, status code, default, environment/config key, database schema or migration, or public SDK signature. No listed breaking change is present. Full details: Only The Ticket Line Carries A Magic Word **Explanation** PASS: The PR description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` followed by `ENG-&lt;number&gt;` or `#&lt;number&gt;`. The only ticket-like text is `No ticket` and the closing note mentions `pull request 9599` without `#`; the `#9599` text appears in PR metadata, not the description. Full details: Coverage Accounts For The Whole Diff **Explanation** PASS — The authoritative diff changes only `docs/self-hosting/advanced/migration.mdx` and deletes the documented `v6.1` section. The description states that this is documentation-only and introduces no product behavior. Its Coverage table contains a `manual` row with a verification method and observed outcome, and its Open gaps subsection is present with an explicit scope note. No rendered product UI changes require media evidence. Comment `@the reviewer help` to get the list of available commands.
#9599
2026-10-20 14:48
coderabbit
review-body
—summary
> [!CAUTION] > Some comments are outside the diff and can’t be posted inline due to GitHub limitations. > > **⚠️ Outside diff range comments (1)** > > > 🟡 Minor · Preserve the skipConsent response-scope exception. · migration.mdx:196 > > `docs/self-hosting/advanced/migration.mdx:196` > **🗄️ Data Integrity & Integration** | **🟡 Minor** | **⚡ Quick win** > > **Preserve the `skipConsent` response-scope exception.** > > The migration excludes existing `skipConsent` registrations. If such a client must retain no-consent authorization and use response scopes, reconnecting is not equivalent: dynamic registration cannot preserve `skipConsent`. Without a manual scope update, the client can fail wi
… the rest (730 characters)th `invalid_scope`. Keep this instruction in the MCP setup guide or another stable self-hosting location. > > > Suggested fix > > ```diff > > The client requested a scope it isn't registered for. Reconnect from scratch so it re-registers > with the scopes the server advertises (`surveys:read`, `surveys:write`, `workflows:read`, > `workflows:write`, `feedbackRecords:read`, `feedbackRecords:write`, `responses:read`, > `responses:write`, `offline_access`). > + > + If an existing OAuth client registered with `skipConsent` must retain no-consent authorization, > + update its registration manually with `responses:read` and/or `responses:write`. Reconnecting > + cannot preserve the `skipConsent` setting. > > ``` > > > > > > > >
#9599
2026-10-20 14:50
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Low impact]** Safe to merge; no blocking or non-blocking findings remain. Reviews (1) · Last reviewed commit: "docs: remove v6.1 section from the self-..." · Reviewed by the reviewer
#9594
2026-10-20 14:52
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The Hub integration now uses generated SDK types and typed endpoint operations. The client adds HubApiError conversion, and list and count operations validate query values. Tests now exercise real client requests and typed service calls. Feedback-record types and reconciliation adjust null handling, and embedded-data extraction checks metadata before accessing embedded_data. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to 9719b This change moves the Hub integration to the new typed SDK while keeping filter serialization, retry safety, and error reporting intact. No outstanding issue was found that would bloc
… the rest (7445 characters)k merging. Security architecture risk: 🔵 Low · up to 9719b The inspected application paths retain dataset authorization, record ownership checks, and explicit response-field allowlists. No introduced security bypass was established. Some retry and failure-recovery behavior remains only partially verified, so the change is not assessed as minimal risk. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The shared client boundary has multi-dataset potential impact because it serves record operations, taxonomy, and tenant-wide deletion using deployment-level credentials. Effective exposure is limited by application authorization and the actual Hub key grants; those deployed grants were not available for inspection. **Trust Boundaries and Controls** - _observed_ — Dataset selection remains an application authorization boundary, not a trusted tenant identifier supplied by the caller. Bare-record operations retain a retrieved-record tenant comparison, and the serializer does not pass arbitrary SDK additions into public responses. **Resilience and Maintainability Implications** - _observed_ — Conflict reconciliation derives the update target from a tenant-scoped submission-and-field lookup, retries an empty lookup once, and reports failure rather than guessing a record identifier. Non-conflict create failures remain explicit per-record failures, not successful reconciliation outcomes. - _observed_ — The integrity-matched SDK request loop keeps the same original request across attempts and refuses transport-error retries for non-idempotent methods or caller-aborted requests. This reduces ambiguous POST replay compared with the base client's broadly retrying transport policy, but does not itself guarantee recovery of a write committed before its response was lost. Pre-merge checks: 5 · 2 Failed checks: 2 warnings | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Breaking Changes Match The Diff | | The Breaking changes checkbox is unticked, but the diff retypes fields in the public v3 feedback-record response. In `apps/web/app/api/v3/feedbackRecords/lib/serializers.ts:42,46-48`, `value_id`, `emo… | Tick `- [x] This PR contains breaking changes` and document the changed v3 response field types, including the affected fields and file. | | Coverage Accounts For The Whole Diff | | The Coverage table does not account for the whole diff. The diff changes reconciliation semantics in `apps/web/lib/feedback-source/reconcile.ts:127-131` by omitting nullable `value_id`, and changes fe… | Add Coverage rows with a verification method and observed outcome for the reconciliation `value_id` mapping, legacy metadata/drawer behavior, all changed Hub service operations (including purge, batch, search, enrichment, and taxonomy), and… | Passed checks: 5 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits syntax, uses a valid type and scope, describes the Hub SDK upgrade and query workaround removal, uses lowercase summary text, and includes a valid ENG-2536 refer… | | Description check | | The description includes the required ticket reference, What & why, review locations, breaking-change assessment, migration and environment notes, testing coverage, open gaps, and risks. It also clear… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Only The Ticket Line Carries A Magic Word | | The only magic-word and ticket pair is the top line, “Fixes ENG-2536”. No other “Fixes”, “Closes”, “Resolves”, or “Ref” appears next to an ENG ticket ID or GitHub issue reference in the description. | Full details: Title check **Explanation** The title follows Conventional Commits syntax, uses a valid type and scope, describes the Hub SDK upgrade and query workaround removal, uses lowercase summary text, and includes a valid ENG-2536 reference. Full details: Description check **Explanation** The description includes the required ticket reference, What & why, review locations, breaking-change assessment, migration and environment notes, testing coverage, open gaps, and risks. It also clearly documents reported failing tests and remaining limitations. Full details: Breaking Changes Match The Diff **Explanation** The Breaking changes checkbox is unticked, but the diff retypes fields in the public v3 feedback-record response. In `apps/web/app/api/v3/feedbackRecords/lib/serializers.ts:42,46-48`, `value_id`, `emotions`, `translation_lang_key`, and `value_text_translated` become narrower than their previous nullable types. `TV3FeedbackRecord` is the DTO returned by the v3 operations. This matches the check's breaking-change rule for retyping an API response field. **Resolution** Tick `- [x] This PR contains breaking changes` and document the changed v3 response field types, including the affected fields and file. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table does not account for the whole diff. The diff changes reconciliation semantics in `apps/web/lib/feedback-source/reconcile.ts:127-131` by omitting nullable `value_id`, and changes feedback-drawer metadata handling in `apps/web/modules/ee/unify-feedback/lib/utils.ts:147-152`; neither behavior has a Coverage row or an Open gap. The Hub service migration also changes tenant purges, batch creates, semantic-search body/query mapping, enrichment, and taxonomy calls (`apps/web/modules/hub/service.ts:162-219`, `302-319`, `406-486`, and later taxonomy wrappers). The table covers list, drawer, delete, retries, errors, and filters, but it does not name verification for these other changed operations. The changed `service.test.ts` and the client configuration tests are not named by a relevant Coverage row; the bare `Rerun` suite command does not name those tests. **Resolution** Add Coverage rows with a verification method and observed outcome for the reconciliation `value_id` mapping, legacy metadata/drawer behavior, all changed Hub service operations (including purge, batch, search, enrichment, and taxonomy), and client configuration/cache/auth behavior. Name the relevant test files or specs in each unit row. Alternatively, list any unverified behavior under Open gaps with a reason. Comment `@the reviewer help` to get the list of available commands.
#9594
2026-10-20 14:59
cubic
review-body
—summary
**2 issues found** across 18 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9594
2026-10-20 14:59
cubic
inline · P2
apps/web/modules/hub/hub-client.test.ts:42defect-functional
After this reset, `getHubClient` reads a fresh `@/lib/env` mock object, but `mutableEnv` still points to the top-level instance; `getClient()` therefore leaves the imported key empty and throws. Hoist a shared mock environment object and mutate that across resets.
#9594
2026-10-20 14:59
cubic
inline · P2
apps/web/modules/hub/utils.ts:66defect-functional
An empty `body.message` falls through to the no-body fallback even though the response contains a body. Fall back to serializing `body` when `body.message` is falsy, matching Stainless. ```suggestion if (bodyMessage) text = typeof bodyMessage === "string" ? bodyMessage : JSON.stringify(bodyMessage); ```
#9594
2026-10-20 15:02
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** The scoped checks support merging, with no blocking findings and the acknowledged behavior differences remaining as documented. Reviews (1) · Last reviewed commit: "refactor(hub): adopt @formbricks/hub 0.1..." · Reviewed by the reviewer
#9597
2026-10-20 15:09
coderabbit
issue-comment
—summary
### Walkthrough The accessibility documentation now reports page-title, language-switching, and semantic-heading fixes in 5.4, and image alt-text and survey top-level-heading fixes in 6.1. It narrows workarounds to versions before the relevant release. It also describes alt-text behavior for question images, ending cards, and Picture Selection options, and notes differences between the current documentation and the August 2026 VPAT. Priority: ⬇️ Low Merge Risk: 🔵 Low · up to a1229 Readers may miss accessibility guidance that remains relevant after 6.1; the impact is limited to documentation accuracy. Architecture risk: 🔵 Low · up to a1229 The change affects 1 system. **Changed syste
… the rest (4686 characters)ms:** `docs` **Architecture concerns** No architecture-level concerns identified. Review details **Systems and components** - _observed_ — docs (service) was modified; 1 changed file maps to changed impact. **Before / after behavior** - _observed_ — Modified behavior in docs/platform/accessibility.mdx: Updated the conformance note to state that page titles, language switching, and semantic headings were fixed in 5.4, image alt text and embedded-survey top-level headings were fixed in 6.1, and the current documentation is more accurate than the report for two later-identified limitations. - _observed_ — Modified behavior in docs/platform/accessibility.mdx: Updated limitation markers and image guidance to distinguish fixes released in 5.4 and 6.1 from report discrepancies. Image alt text is now documented as available for question images, ending cards, and Picture Selection options; empty text makes images decorative, while empty Picture Selection text retains positional labels. The workaround now applies specifically to versions before 6.1. - _observed_ — Modified behavior in docs/platform/accessibility.mdx: Changed the page-title status from “Fixed, not yet released” to “Fixed in 5.4.” - _observed_ — Modified behavior in docs/platform/accessibility.mdx: Changed the page-instruction workaround to apply to versions before 5.4 rather than all released versions. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows the Conventional Commits format, uses the docs type, describes the accessibility documentation update, uses lowercase text, and has no trailing period. | | Description check | | The description includes the required sections, explains the documentation changes, identifies the absence of a ticket, documents breaking changes and migrations, and records test coverage and open ga… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The checkbox is unticked, and the PR changes only `docs/platform/accessibility.mdx`. The diff contains documentation text changes only. It does not change an API shape, route, webhook, status code, de… | | Only The Ticket Line Carries A Magic Word | | PASS. The description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with `ENG-<number>` or `#<number>` outside the top ticket context. The only Linear ID is `ENG-3334` in “original page w… | | Coverage Accounts For The Whole Diff | | PASS. The reviewed diff changes only `docs/platform/accessibility.mdx`; it does not change product behavior. The description states “None: docs text only.” The “How this was tested” section is present… | Full details: Description check **Explanation** The description includes the required sections, explains the documentation changes, identifies the absence of a ticket, documents breaking changes and migrations, and records test coverage and open gaps. Full details: Breaking Changes Match The Diff **Explanation** The checkbox is unticked, and the PR changes only `docs/platform/accessibility.mdx`. The diff contains documentation text changes only. It does not change an API shape, route, webhook, status code, default, configuration key, database schema, or SDK signature. Full details: Only The Ticket Line Carries A Magic Word **Explanation** PASS. The description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with `ENG-&lt;number&gt;` or `#&lt;number&gt;` outside the top ticket context. The only Linear ID is `ENG-3334` in “original page work (ENG-3334) is already closed,” and the GitHub references (`#9574`, `#9376`, `#9533`) have no magic word next to them. Full details: Coverage Accounts For The Whole Diff **Explanation** PASS. The reviewed diff changes only `docs/platform/accessibility.mdx`; it does not change product behavior. The description states “None: docs text only.” The “How this was tested” section is present, its Coverage table has two manual rows with observed outcomes for the 5.4 and 6.1 markers, and “Open gaps” is present with the rendering limitation. No explicit coverage failure condition applies. Comment `@the reviewer help` to get the list of available commands.
#9597
2026-10-20 15:11
coderabbit
inline · P2
docs/platform/accessibility.mdx:90docs
**Clarify the workaround rule for current embedded surveys.** These lines say every workaround applies only to older versions. The semantic-heading workaround at Line 228 also applies to website and in-app surveys after 6.1 because their top-level heading remains generic. State that exception here so readers do not dismiss guidance that still applies. (github.com)
#9597
2026-10-20 15:11
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9597
2026-10-20 15:13
cubic
inline · P2
docs/platform/accessibility.mdx:91docs
The semantic-headings workaround also applies to website and in-app surveys on current versions, so this blanket rule can lead readers to drop guidance that remains relevant. Qualify the rule to allow the ongoing exceptions named in each entry. ```suggestion closed in Formbricks 5.4, and **Fixed in 6.1** that it was closed in 6.1; the workaround is for older versions unless an entry explicitly says otherwise. **Not in the 9 August 2026 report** means the report records ```
#9597
2026-10-20 15:13
cubic
review-body
—summary
**1 issue found** across 1 file Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9597
2026-10-20 15:20
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Low impact]** The changes affect readers of one accessibility page\. Safe to merge alongside the intended 6.1 release, with one non-blocking documentation clarification. Findings 1. **Workaround guidance excludes current versions** Reviews (1) · Last reviewed commit: "docs: mark the 5.4 accessibility fixes a..." · Reviewed by the reviewer
#9597
2026-10-20 15:20
greptile
inline · P2
docs/platform/accessibility.mdx:91docs
**Workaround guidance excludes current versions** The guide says workarounds apply only to older versions, but the Semantic Headings workaround still applies to website and in-app surveys in 6.1 because their heading remains generic. Creators on current versions could skip advice they still need. This is a non-blocking documentation inconsistency; qualify the guide to preserve the continuing website/in-app exception. ```suggestion Three markers say where an entry differs from the 9 August 2026 report. **Fixed in 5.4** means the gap was closed in Formbricks 5.4, and **Fixed in 6.1** that it was closed in 6.1. Workarounds apply to older versions, except the generic-heading workaround for web
… the rest (3379 characters)site and in-app surveys, which still applies in current versions. **Not in the 9 August 2026 report** means the report records ``` Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! Artifacts **Recording of the check** - Chromium visits the parent revision’s marker guide and Semantic Headings workaround, showing that the guide still includes released versions. **Recording of the check** - Chromium visits the changed marker guide and retained heading workaround, showing the older-versions-only statement alongside continuing website/in-app advice. **Marker guide before the change** - The rendered parent guide says the workaround still applies to released versions, establishing the comparison baseline. **Marker guide at the supplied head** - The rendered head guide says the workaround applies only to older versions, confirming the reported blanket restriction. **Heading workaround before the change** - The parent page displays heading advice for released versions and website/in-app surveys, establishing the original scope. **Heading workaround at the supplied head** - The head page retains explicit website/in-app heading advice, confirming the exception omitted by the marker guide. **Command output from the check** - The executed Chromium check records HTTP 200, rendered DOM text and HTML, and exit code 0, verifying the baseline. **Command output from the check** - The executed Chromium check records both conflicting passages and exit code 0, providing the decisive confirmation. **Evidence from the check** - The Playwright source navigates both revisions, records videos and screenshots, extracts the relevant DOM, and asserts the reported text. **Evidence from the check** - The shell runner invokes the Chromium capture and records its command, working directory, and exit code in each proof log. **Evidence from the check** - The preparation script checks the head, prints numbered source locations, and extracts parent documentation without modifying product code. **Command output from the check** - The preparation output records the supplied head, actual diff, numbered source passages, and successful exit, tying the rendered contradiction to its source. **Evidence from the check** - The preview script serves parent and head documentation on separate local ports, enabling the same-scope comparison. **Command output from the check** - The Mint process reports preview readiness on port 3001 after startup warnings, and the subsequent browser check successfully renders the target page. **Command output from the check** - The Mint process reports preview readiness on port 3000, and the subsequent browser check successfully renders the target page. **Evidence from the check** - The diagnostic script captures the actual browser body and screenshot to resolve an initial locator mismatch before the final successful checks. **Command output from the check** - The diagnostic run records the rendered page body and exit code 0, confirming that the documentation loaded despite the initial locator mismatch. **Accessibility page during browser diagnosis** - The diagnostic browser screenshot captures the loaded accessibility page, distinguishing a test-locator issue from a page-render failure. Ran code and verified through TREX
#9603
2026-10-20 15:22
coderabbit
issue-comment
—summary
### Walkthrough The Prisma schema retains `Survey.variables` and `Survey.hiddenFields` with `@ignore`. The PR removes the migration and helper that dropped these columns. The backfill integration suites still restore missing columns before tests, but no longer drop them afterward. The migration-order fixture no longer lists the drop migration. Priority: ⬇️ Low Merge Risk: 🟡 Moderate · up to 6501f Databases that previously applied the withdrawn migration can have upgrades blocked and remain incompatible with Embedded Data backfills until their migration history and columns are repaired. Security architecture risk: 🔵 Low · up to 6501f Retaining the columns removes a destructive upgrade
… the rest (5712 characters) step. However, column availability alone does not establish that mixed-version writes or rollback preserve recent survey-field changes. The identified uncertainty concerns compatibility and recovery; no new access-control bypass was established in the reviewed paths. **Retained concerns** - **Medium · reliability · inferred:** The newly supported mixed-version and rollback lifecycle preserves schema compatibility without establishing data synchronization. Current saves update Embedded Data rows rather than the ignored legacy columns. An older pod that consumes those columns can therefore observe stale definitions; column-only edits after backfill can likewise remain invisible to current readers. Applied backfills do not continuously reconcile these stores, and linked surveys are deliberately skipped. The row-only implementation predates this PR, but retaining the columns enables the compatibility path in which this divergence matters. Effective exposure depends on the uninspected released 6.0 behavior. Security review details **Security Blast Radius** - _inferred_ — The compatibility risk follows the shared application deployment and Survey database rather than a new external entrypoint. If column-based older consumers coexist with current row-based consumers, inconsistent field definitions can affect surveys across workspaces in that deployment. No privilege gain or cross-workspace access path was established. **Trust Boundaries and Controls** - _observed_ — The inspected current update path scopes its database mutation by survey and stored workspace identity, passes that stored workspace identity into reconciliation rather than accepting it from the client, and returns the post-reconciliation state. The PR does not change this control boundary. **Hardening Proposals** - _proposed_ — Define the writable-version cutover and rollback reconciliation policy explicitly. If supported older versions use the retained columns, preserve synchronization during coexistence or constrain writes until cutover, and provide a recovery path for changes made before rollback. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format with the valid `fix(database)` type and scope. It clearly describes retaining the legacy Survey columns until Formbricks 7, and it has no trailing period. | | Description check | | The description includes the required ticket reference, What & why, review links, breaking-change status, migration guidance, test coverage, open gaps, and risks. It accurately explains the retained c… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR leaves the Breaking changes checkbox unticked, and the diff introduces none of the listed breaking changes. It adds `Survey.hiddenFields` and `Survey.variables` as ignored Prisma fields, delete… | | Only The Ticket Line Carries A Magic Word | | The description contains one allowed magic-word pair: `Ref ENG-2404` on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears next to an `ENG-<number>` or `#<number… | | Coverage Accounts For The Whole Diff | | The description contains a non-empty Coverage table. It covers retained columns and row compatibility, application operation after upgrade, schema/migration alignment, and removal of the drop migratio… | Full details: Description check **Explanation** The description includes the required ticket reference, What & why, review links, breaking-change status, migration guidance, test coverage, open gaps, and risks. It accurately explains the retained columns, removed migration, local migration cleanup, validation results, and rolling-upgrade limitation. Full details: Breaking Changes Match The Diff **Explanation** The PR leaves the Breaking changes checkbox unticked, and the diff introduces none of the listed breaking changes. It adds `Survey.hiddenFields` and `Survey.variables` as ignored Prisma fields, deletes an unreleased migration that would have dropped those columns, and removes internal test helpers. No endpoint, environment key, public SDK signature, request/response shape, or database drop is introduced. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The description contains one allowed magic-word pair: `Ref ENG-2404` on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Coverage Accounts For The Whole Diff **Explanation** The description contains a non-empty Coverage table. It covers retained columns and row compatibility, application operation after upgrade, schema/migration alignment, and removal of the drop migration. Each row has a verification method and observed outcome. The unit row names `lib/authzed/survey-projection-migration.test.ts` in the Rerun command. The description also lists the untested cluster rolling-upgrade scenario under Open gaps. The diff changes database schema, migration state, and test setup; it does not change rendered UI. Comment `@the reviewer help` to get the list of available commands.
#9603
2026-10-20 15:23
cubic
review-body
—summary
**No issues found** across 6 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9603
2026-10-20 15:28
coderabbit
inline · P1
packages/database/schema/main.prisma:416defect-data
**Preserve the recorded migration and restore the dropped columns.** When a database has recorded `20260928120001_eng_2404_drop_survey_legacy_embedded_data_columns` and has a later schema migration pending, `migration-runner.ts` stages only directories present in the checkout before calling `prisma migrate deploy`. The deleted directory then causes Prisma migration-history divergence and blocks deployment. `@ignore` does not recreate `Survey.variables` or `Survey.hiddenFields`. The Embedded Data backfills still execute raw SQL that selects both columns, so they cannot run against a database where the columns were already dropped. The test-only repair helper does not fix deployed databases,
… the rest (349 characters) and deleting the `_prisma_migrations` row only removes the local history mismatch. Restore the exact original migration directory to preserve its checksum, then add a later forward migration that recreates both JSON columns with the schema defaults. This preserves rolling-upgrade compatibility and repairs databases that already applied the drop.
#9603
2026-10-20 15:28
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9603
2026-10-20 15:30
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No merge-blocking issue identified. The retained columns match their original definitions, and the exercised survey operations and backfills succeeded with those columns retained. Reviews (1) · Last reviewed commit: "fix(database): keep the legacy Survey co..." · Reviewed by the reviewer
#9580
2026-10-20 15:33
coderabbit
issue-comment
—summary
### Walkthrough The response keyset index migration now documents Prisma’s parser-dependent transaction behavior and sets `lock_timeout` to `0`, while leaving `statement_timeout` unset. A new test checks that migrations containing concurrent index builds set `lock_timeout` to `0` or `'0'`. The README explains the migration behavior and provides the checksum and SQL update for development databases that applied the earlier migration version. Priority: ⬇️ Low Merge Risk: 🔵 Low · up to 760c8 The migration can proceed, but strengthen the guard so a future migration cannot pass it without using an effective zero timeout. Security architecture risk: 🔵 Low · up to 760c8 Unlimited waiting re
… the rest (5202 characters)duces avoidable index-build failures without introducing new access or privileges. The remaining risk is operational: a stalled deployment depends on external cancellation and recovery procedures. **Retained concerns** - **Low · reliability · inferred:** Removing the database-side lock-wait limit allows a deployment to remain waiting indefinitely. Termination and recovery therefore depend on external controls whose production behavior was not established. This is a bounded rollout-containment concern, not an observed authorization vulnerability. Security review details **Security Blast Radius** - _inferred_ — The direct exposure is the migration session and database rollout progress. Writers to Response or older snapshots elsewhere in the same database can prolong the build; the reviewed change does not establish a new remotely reachable mechanism for creating those blockers. **Trust Boundaries and Controls** - _observed_ — The existing runner supplies the migration database URL to the Prisma deployment subprocess. This PR does not change that credential boundary or grant the policy test database execution authority. **Resilience and Maintainability Implications** - _inferred_ — The zero-timeout policy reduces timeout-induced invalid indexes, while the existing validity guard contains silent acceptance after interruption. Recovery remains operator-driven rather than atomic rollback of all three builds. **Hardening Proposals** - _proposed_ — Validate that production deployment cancellation terminates the PostgreSQL build session, and document how operators distinguish a healthy long wait from a stalled deployment before performing invalid-index recovery. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows the Conventional Commits format, uses an allowed type and scope, describes the lock_timeout migration change, uses a lowercase summary apart from required code identifiers, and inclu… | | Description check | | The description includes the required ticket reference, change summary, affected files, breaking-change assessment, migration guidance, test coverage, open gaps, risks, and rerun command. The content … | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The Breaking changes checkbox is unticked. The diff only modifies README documentation and the existing `20260909120000_response_keyset_index_tiebreakers/migration.sql`, and adds a guard test. It does… | | Only The Ticket Line Carries A Magic Word | | The only magic-word pair in the pull request description is “Fixes ENG-3701” on the ticket line at the top. The later references are migration links such as “#L58”, not GitHub issue references. | | Coverage Accounts For The Whole Diff | | The diff changes the migration timeout, adds a guard test, and adds the development checksum procedure. The Coverage table addresses these changes: the unit row names `concurrent-index-migrations`, gi… | Full details: Title check **Explanation** The title follows the Conventional Commits format, uses an allowed type and scope, describes the lock_timeout migration change, uses a lowercase summary apart from required code identifiers, and includes the permitted ENG-3701 reference. Full details: Description check **Explanation** The description includes the required ticket reference, change summary, affected files, breaking-change assessment, migration guidance, test coverage, open gaps, risks, and rerun command. The content matches the pull request objectives. Full details: Breaking Changes Match The Diff **Explanation** The Breaking changes checkbox is unticked. The diff only modifies README documentation and the existing `20260909120000_response_keyset_index_tiebreakers/migration.sql`, and adds a guard test. It does not add a database migration, change an API or SDK surface, alter an endpoint, change an environment/config default, or remove/retype database fields. The documented development-database checksum update does not match the listed breaking-change conditions. Full details: Coverage Accounts For The Whole Diff **Explanation** The diff changes the migration timeout, adds a guard test, and adds the development checksum procedure. The Coverage table addresses these changes: the unit row names `concurrent-index-migrations`, gives the rerun command and outcome; manual rows verify the old timeout failure, the new wait and valid indexes, full migration history, and the checksum update. The `Open gaps` subsection is present and states the production-sized table timing gap. No rendered UI changes are present. Comment `@the reviewer help` to get the list of available commands.
#9580
2026-10-20 15:37
cubic
inline · P2
packages/database/src/scripts/concurrent-index-migrations.test.ts:14tests
This guard can pass while the concurrent build runs without the required timeout: `SET LOCAL lock_timeout = 0` expires before the next autocommitted statement, and a `SET lock_timeout = 0` after the build also satisfies the value check. Require an effective session-level zero setting before each concurrent build.
#9580
2026-10-20 15:37
cubic
review-body
—summary
**1 issue found** across 3 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9580
2026-10-20 15:37
coderabbit
inline · P2
packages/database/src/scripts/concurrent-index-migrations.test.ts:33tests
**Check the timeout in effect before each concurrent build.** This assertion accepts `CREATE INDEX CONCURRENTLY ...; SET lock_timeout = 0;`. It also accepts `SET LOCAL lock_timeout = 0` before the build, although `SET LOCAL` has no effect outside a transaction block. Both migrations pass this guard while the build can still use a nonzero session timeout. Check statement order and reject `SET LOCAL` for these autocommit builds. (github.com) As per path instructions, “Judge tests by confidence, not coverage.” _Source: Path instructions_
#9580
2026-10-20 15:37
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9580
2026-10-20 15:39
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The change affects database deploys that build the Response indexes\. No blocking issue was established; the current migration has the correct timeout ordering, and the remaining test-coverage concern is non-blocking. Findings 1. **Guard misses timeout ordering** Reviews (1) · Last reviewed commit: "fix(database): build the keyset Response..." · Reviewed by the reviewer
#9580
2026-10-20 15:39
greptile
inline · P2
packages/database/src/scripts/concurrent-index-migrations.test.ts:33tests
**Guard misses timeout ordering** The test checks timeout values anywhere in the file, not the value in effect when each index builds. Moving `SET lock_timeout = 0` below the concurrent builds, or moving `RESET lock_timeout` above them, still passes both existing tests. A future migration could therefore pass this guard while its builds run under a short timeout. The current migration has the correct ordering; this is a non-blocking regression-test gap. Check statements in order and require the timeout to remain disabled at each build. Add tests for a late `SET` and an early `RESET`. Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remem
… the rest (5282 characters)ber it for next time! Artifacts **Executed mutation-check script** - The executed Python harness runs the repository guard on the original migration and both requested mutations, captures exact source and output, and restores the migration. **Mutation-check execution output** - Running the captured harness returned exit 0 and recorded successful test exits for the baseline and both mutations, confirming completion of the requested checks. **Existing guard output with correct statement ordering** - The unchanged Vitest guard ran against the original migration and passed both tests, establishing the baseline. **Existing guard output with late SET and early RESET** - The same unchanged Vitest guard ran once against each incorrect ordering and passed both tests each time, confirming the ordering gap. **Tracked-file restoration verification** - Byte-equality assertions and Git checks after mutation testing verified restoration of the migration and an unchanged guard, leaving no tracked modifications. **Executed Python source** - Complete source of the executed mutation harness, including the actual guard invocation and restoration checks. View Log ``` #!/usr/bin/env python3 """Run the actual repository guard once per input; restore exact migration bytes.""" from pathlib import Path import hashlib import shlex import subprocess ROOT = Path('/home/user/repo') OUT = ROOT / 'trex-artifacts' MIGRATION = ROOT / 'packages/database/migration/20260909120000_response_keyset_index_tiebreakers/migration.sql' GUARD = ROOT / 'packages/database/src/scripts/concurrent-index-migrations.test.ts' COMMAND = ['timeout', '60', 'pnpm', '--filter', '@formbricks/database', 'exec', 'vitest', 'run', 'src/scripts/concurrent-index-migrations.test.ts'] original = MIGRATION.read_bytes() guard_original = GUARD.read_bytes() sql = original.decode() assert sql.count('SET lock_timeout = 0;') == 1 assert sql.count('RESET lock_timeout;') == 1 assert sql.count('CREATE INDEX CONCURRENTLY') == 3 def numbered(data): return ''.join(f'{i}: {line}\n' for i, line in enumerate(data.splitlines(), 1)) def capture(label, content): MIGRATION.write_text(content) result = subprocess.run(COMMAND, cwd=ROOT, capture_output=True, text=True) record = (f'Command: {shlex.join(COMMAND)}\nWorking directory: {ROOT}\n' f'Exit code: {result.returncode}\nCondition: {label}\n\n' f'Executed guard: {GUARD.relative_to(ROOT)}\n{numbered(GUARD.read_text())}\n' f'Migration read by guard: {MIGRATION.relative_to(ROOT)}\n{numbered(MIGRATION.read_text())}\n' f'STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}\n') print(f'{label}: exit={result.returncode}', flush=True) return record, result.returncode codes = [] try: before, code = capture('Original ordering: SET, three builds, RESET', sql) (OUT / 'lock-timeout-ordering-01-before.log').write_text(before) codes.append(code) # Move the existing SET after all three CREATEs without otherwise changing SQL. late_set = sql.replace('SET lock_timeout = 0;\n', '') last_build = 'CREATE INDEX CONCURRENTLY IF NOT EXISTS "Response_contactId_created_at_id_idx" ON "Response"("contactId", created_at, id);' late_set = late_set.replace(last_build, last_build + '\nSET lock_timeout = 0;') assert late_set.index('CREATE INDEX CONCURRENTLY') < late_set.index('SET lock_timeout = 0;') after_late, code = capture('Mutation 1: three builds, SET, RESET', late_set) codes.append(code) # Independently start from original SQL and move RESET before the builds. early_reset = sql.replace('RESET lock_timeout;\n', '') early_reset = early_reset.replace('SET lock_timeout = 0;', 'SET lock_timeout = 0;\nRESET lock_timeout;') assert early_reset.index('SET lock_timeout = 0;') < early_reset.index('RESET lock_timeout;') < early_reset.index('CREATE INDEX CONCURRENTLY') after_reset, code = capture('Mutation 2: SET, RESET, three builds', early_reset) codes.append(code) (OUT / 'lock-timeout-ordering-02-after.log').write_text(after_late + '\n' + after_reset) finally: MIGRATION.write_bytes(original) assert MIGRATION.read_bytes() == original assert GUARD.read_bytes() == guard_original check_command = ['git', 'diff', '--exit-code', '--'] check = subprocess.run(check_command, cwd=ROOT, capture_output=True, text=True) status_command = ['git', 'status', '--short'] status = subprocess.run(status_command, cwd=ROOT, capture_output=True, text=True) (OUT / 'lock-timeout-ordering-restoration.log').write_text( f'Command: {shlex.join(check_command)}\nWorking directory: {ROOT}\nExit code: {check.returncode}\n' f'STDOUT:\n{check.stdout}\nSTDERR:\n{check.stderr}\n' f'Command: {shlex.join(status_command)}\nWorking directory: {ROOT}\nExit code: {status.returncode}\n' f'STDOUT:\n{status.stdout}\nSTDERR:\n{status.stderr}\n' f'Migration original/restored SHA256: {hashlib.sha256(original).hexdigest()}\n' f'Guard unchanged SHA256: {hashlib.sha256(guard_original).hexdigest()}\n') print(f'Restored original migration bytes; git diff exit={check.returncode}', flush=True) assert codes == [0, 0, 0], codes ``` Ran code and verified through TREX
#9604
2026-10-20 15:41
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The adapter now reads the idle transaction timeout from `DATABASE_URL`. It omits the startup parameter when the value is absent, zero, or invalid. Valid nonzero values are forwarded, including when `pgbouncer=true`, with a warning. Application client options no longer apply a 60-second default. Tests and documentation now cover the URL-based configuration and PostgreSQL server defaults. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to f7789 The idle transaction timeout becomes opt-in through DATABASE_URL, and unconfigured deployments use the PostgreSQL server default. No concrete merge-blocking risk was identif
… the rest (8118 characters)ied. Security architecture risk: 🔵 Low · up to f7789 Database permissions and credential targets do not expand. The principal tradeoff is reduced automatic cleanup of stalled transactions when no server-side policy is configured. Explicit URL settings also reach migration connections, so compatibility and migration safety depend on deployment configuration. **Retained concerns** - **Low · reliability · inferred:** For installations previously receiving the application’s 60-second timeout, omitting the URL setting now removes that independent cleanup bound. If PostgreSQL’s effective default is disabled and a process stalls with an open transaction, locks and connection capacity can remain occupied until another recovery mechanism intervenes. This weakens failure containment, although no concrete attacker-triggered stall was established and normal transaction deadlines remain unchanged. Security review details **Security Blast Radius** - _inferred_ — The relevant failure scope is sessions in the configured database, their connection pools, and work blocked by locks those sessions retain. Shared-database workloads may therefore be affected beyond the originating operation; tenant-specific isolation and deployed exposure cannot be determined from the supplied configuration. **Security Findings and Attack Paths** - _observed_ — The inspected configuration flow begins with an explicit adapter argument or process environment, not a request parameter. The flagged public-entrypoint ranges are test helpers. No concrete request-to-stalled-transaction attack path was established, and the canonical security input contains no retained findings; its overall coverage remains unknown. **Trust Boundaries and Controls** - _observed_ — The authority transition is from operator-managed URL configuration into a PostgreSQL session startup setting. Numeric validation and connection-string stripping constrain that setting, but omission or invalid input deliberately delegates enforcement to server policy rather than failing startup. **Resilience and Maintainability Implications** - _inferred_ — Normal acquisition and application transaction deadlines remain intact, but they cannot guarantee cleanup while the application is stalled. Responsibility for that independent safeguard now rests with server policy or explicit URL configuration. Migration timeout failures retain the existing transactional bookkeeping and retry path, without proving that every migration’s external effects are atomic. **Hardening Proposals** - _proposed_ — For deployments that require bounded cleanup of stalled transactions, establish an explicit server-side policy or a verified compatible URL setting. Validate the effective setting through the actual pooler, keep migration policy separate where needed, and include the base version’s timeout behavior in rollback checks. Pre-merge checks: 5 · 1 · 1 Failed checks: 1 warning · 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Breaking Changes Match The Diff | | The checkbox is unticked, but the diff changes a default value. Before this PR, `createAppPrismaClientOptions` passed the 60,000 ms `APP_IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS` default. The PR removes… | Tick `- [x] This PR contains breaking changes` and document that the default `idle_in_transaction_session_timeout` behavior changed from 60,000 ms to no startup parameter unless `DATABASE_URL` configures it. | | Only The Ticket Line Carries A Magic Word | | The supplied PR description is explicitly truncated. The visible text contains the allowed top-line pair `Ref ENG-3285`, but the full description is required to confirm that no other `Fixes`, `Closes`… | Provide the complete raw PR description, or make the omitted description available for scanning. | Passed checks: 5 passed | Check name | Status | Explanation | | :---------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses an allowed type and scope, describes the change accurately, uses lowercase prose, and includes a valid ENG ticket reference. | | Description check | | The description includes the required ticket reference, change summary, review links, breaking-change status, migration details, test coverage, open gaps, risks, and AI note. It is complete and direct… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Coverage Accounts For The Whole Diff | | The description includes a non-empty Coverage table and an Open gaps subsection. The rows cover the changed behaviors: URL-only opt-in and client defaults, valid/zero/blank/invalid URL values with war… | Full details: Description check **Explanation** The description includes the required ticket reference, change summary, review links, breaking-change status, migration details, test coverage, open gaps, risks, and AI note. It is complete and directly matches the changes. Full details: Coverage Accounts For The Whole Diff **Explanation** The description includes a non-empty Coverage table and an Open gaps subsection. The rows cover the changed behaviors: URL-only opt-in and client defaults, valid/zero/blank/invalid URL values with warnings, and PostgreSQL startup behavior plus idle-transaction termination. Each row names a test or spec through the row or Rerun commands and reports an outcome. The gaps list the untested full app PgBouncer path and other pooler versions. The diff changes backend behavior, tests, and documentation; it does not change rendered product UI, so no screenshot is required. Full details: Breaking Changes Match The Diff **Explanation** The checkbox is unticked, but the diff changes a default value. Before this PR, `createAppPrismaClientOptions` passed the 60,000 ms `APP_IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS` default. The PR removes that default, and `resolveIdleInTransactionSessionTimeout` now returns `undefined` when the URL omits the parameter (`packages/database/src/prisma-adapter.ts:185-195`). This changes the default emitted PostgreSQL startup configuration. **Resolution** Tick `- [x] This PR contains breaking changes` and document that the default `idle_in_transaction_session_timeout` behavior changed from 60,000 ms to no startup parameter unless `DATABASE_URL` configures it. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The supplied PR description is explicitly truncated. The visible text contains the allowed top-line pair `Ref ENG-3285`, but the full description is required to confirm that no other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears later. **Resolution** Provide the complete raw PR description, or make the omitted description available for scanning. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 6 - Waived: $1.50 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9604
2026-10-20 15:47
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No blocking issues were found in the checked database connection behavior; the acknowledged trade-offs are intentional. Reviews (1) · Last reviewed commit: "fix(database): make the idle-transaction..." · Reviewed by the reviewer
#9604
2026-10-20 15:48
cubic
inline · P2
apps/web/integration/transaction-budgets.integration.test.ts:1defect-functional
Declare `pg` in `apps/web/package.json`; this import cannot rely on a dependency declared only by `@formbricks/database` under pnpm's isolated dependency layout.
#9604
2026-10-20 15:48
cubic
review-body
—summary
**1 issue found** across 6 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9605
2026-10-20 15:49
coderabbit
issue-comment
—summary
### Walkthrough Cube.js runtime dependencies and fallback archive checksums were updated from version 1.7.47 to 1.7.50. The Cube image revision and image tags were also updated in the development and Docker Compose configurations, Helm values, and README examples. Priority: ⬇️ Low Merge Risk: 🟡 Moderate · up to 88126 The chart default update may leave self-hosters unaware of the new Cube image and migration guidance; add the required breaking-change entry before merging. Security architecture risk: 🔵 Low · up to 88126 The deployment settings retain their existing credentials, network exposure, and security restrictions, while image versions and tags remain aligned. No introduced secu
… the rest (7000 characters)rity concern was established. Risk remains low rather than minimal because the upgraded dependency’s internal behavior and the published image were not independently verified. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The upgraded runtime executes within Cube’s existing credential and service boundaries, including configured PostgreSQL access and API-secret handling. Consequently, changed upstream behavior could affect data reachable by those credentials; the inspected bindings do not establish broader authority or quantify independently attackable tenant scope. **Trust Boundaries and Controls** - _observed_ — Native fallback downloads remain subject to architecture-specific checksum verification before the image can build. This checks consistency with the committed digests; it does not independently establish the provenance of the newly supplied digest values. **Resilience and Maintainability Implications** - _inferred_ — The existing publisher has a recovery limitation: interruption after creating the version-revision tag but before signing can leave an unsigned image that reruns do not repair, because an existing tag skips both pushing and signing. Partial latest-tag publication is likewise not explicitly repaired. This workflow is unchanged from the PR base, and inspected deployment consumers use version-revision tags rather than latest; this is not an introduced or worsened PR concern. Pre-merge checks: 5 · 2 Failed checks: 2 warnings | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Breaking Changes Match The Diff | | The checkbox is unticked, but the diff changes the chart's default Cube image tag from `1.7.47-3` to `1.7.50-1` in `charts/formbricks/values.yaml:1089`. The check defines a changed default value as br… | Tick `- [x] This PR contains breaking changes` in the `## Breaking changes` section and document the changed default at `charts/formbricks/values.yaml:1089`. | | Coverage Accounts For The Whole Diff | | The Coverage table has one valid manual row for building the Cube image, and the Open gaps subsection explicitly says “none.” However, the diff also changes the runtime image selected by `docker-compo… | Add Coverage rows for the updated development, Docker Compose, and Helm image references. State the verification method and observed outcome for each, such as starting the affected deployment paths with the new image. Alternatively, list ea… | Passed checks: 5 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses a permitted type and scope, stays lowercase, and describes the Cube dependency and image update. | | Description check | | The description includes the required ticket explanation, change summary, review location, breaking-change status, migration and environment status, test command, coverage, gaps, and risks. The genera… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Only The Ticket Line Carries A Magic Word | | The description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with an `ENG-<number>` or `#<number>` outside a ticket line. The only issue reference is `#9545`, preceded by `Replaces`, whi… | Full details: Description check **Explanation** The description includes the required ticket explanation, change summary, review location, breaking-change status, migration and environment status, test command, coverage, gaps, and risks. The generated summary also documents the related image-reference updates. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with an `ENG-&lt;number&gt;` or `#&lt;number&gt;` outside a ticket line. The only issue reference is `#9545`, preceded by `Replaces`, which is not a listed magic word. Full details: Breaking Changes Match The Diff **Explanation** The checkbox is unticked, but the diff changes the chart's default Cube image tag from `1.7.47-3` to `1.7.50-1` in `charts/formbricks/values.yaml:1089`. The check defines a changed default value as breaking, including values in `charts/formbricks/values.yaml`. **Resolution** Tick `- [x] This PR contains breaking changes` in the `## Breaking changes` section and document the changed default at `charts/formbricks/values.yaml:1089`. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table has one valid manual row for building the Cube image, and the Open gaps subsection explicitly says “none.” However, the diff also changes the runtime image selected by `docker-compose.dev.yml`, `docker/docker-compose.yml`, and `charts/formbricks/values.yaml`, plus the published image revision and native archive checksums. The description gives no verification outcome for running or deploying those updated image references, and it lists no gap for them. **Resolution** Add Coverage rows for the updated development, Docker Compose, and Helm image references. State the verification method and observed outcome for each, such as starting the affected deployment paths with the new image. Alternatively, list each unverified runtime behavior under Open gaps with a reason. Keep the existing build row for the dependency and native-archive changes. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 7 - Waived: $1.75 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. > [!WARNING] > ⚠️ This pull request shows signs of AI-generated slop (description_diff_mismatch). It has been flagged by the reviewer slop detection and should be reviewed carefully. Comment `@the reviewer help` to get the list of available commands.
#9605
2026-10-20 15:53
coderabbit
inline · P2
charts/formbricks/values.yaml:1089docs
**Add the breaking-change entry to the PR description.** The chart changes the default `cube.image.tag`. The repository requires a checked breaking-change entry for default changes. Add the explanation below instead of reverting the chart value. Suggested fix ```diff -- [ ] This PR contains breaking changes +- [x] This PR contains breaking changes -None +| Change | Impact | +| --- | --- | +| `cube.image.tag` changes from `1.7.47-3` to `1.7.50-1` | Self-hosters using the chart default receive a new Cube image. | ```
#9605
2026-10-20 15:53
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9605
2026-10-20 15:59
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge based on successful before-and-after image builds, native-module execution, canonical smoke tests, and authenticated analytics checks. Reviews (1) · Last reviewed commit: "chore(docker): bump the cube group in /d..." · Reviewed by the reviewer
#9605
2026-10-20 16:00
cubic
inline · P2
charts/formbricks/values.yaml:1089docs
Mark this default image-tag change as a breaking change in the PR description; the repository classifies default changes as breaking.
#9605
2026-10-20 16:00
cubic
review-body
—summary
**1 issue found** across 8 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9606
2026-10-20 16:01
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The updater now warns and continues when `hub-worker` is absent. It still stops if the AuthZed services checks fail, and it runs the legacy Valkey migration only after those checks pass. Tests cover both paths. The Docker and self-hosting documentation describes the updated behavior and upgrade steps. Priority: ➖ Normal **Severity of issue fixed:** Medium Merge Risk: ⚪ Minimal · up to a209e The updater can continue without a Hub worker while retaining the other update checks. No merge-blocking issue was identified. Architecture risk: 🔵 Low · up to a209e The change affects 2 systems. **Changed systems:**
… the rest (6621 characters)`docker`, `docs` **Architecture concerns** No architecture-level concerns identified. Review details **Systems and components** - _observed_ — docker (service) was modified; 3 changed files map to changed impact. - _observed_ — docs (service) was modified; 2 changed files map to changed impact. **Before / after behavior** - _observed_ — Modified behavior in docker/README.md: Existing installations may run the updater without `hub-worker`; it now warns and continues rather than stopping before pulling or restarting. The documentation changes the migration-guide anchor and states that the worker will be required in the next major release. - _observed_ — Modified behavior in docker/__tests__/formbricks-script.test.ts: Renamed the test scenario from blocking updates for a missing Hub worker to warning and continuing the update. - _observed_ — Modified behavior in docker/__tests__/formbricks-script.test.ts: Changed expectations for a missing Hub worker: the update now succeeds with a warning and migration link, pulls images, runs migration, rewrites only the Valkey pin, and leaves `hub-worker` absent. - _observed_ — Modified behavior in docker/__tests__/formbricks-script.test.ts: Added coverage for a missing Hub worker followed by a missing AuthZed services gate; the update must fail at the AuthZed gate and leave the Compose file and Valkey backup unchanged. Pre-merge checks: 5 · 1 · 1 Failed checks: 1 warning · 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Coverage Accounts For The Whole Diff | | The Coverage table includes the changed update behaviors and observed outcomes, but the row “A stopped update leaves the Compose file untouched” is marked `unit (mutation)` without naming the mutated … | Update that Coverage row to name the mutation target, for example: `unit (mutation: move docker/formbricks.sh:1342, migrate_legacy_valkey_image, above the AuthZed gates)`; retain the observed outcome that the test turns red and rerun the na… | | Only The Ticket Line Carries A Magic Word | | The visible description contains only the allowed top line `Ref ENG-2484`; other visible issue references such as `#9379` have no magic word. However, the supplied description is explicitly marked tru… | Provide the complete raw PR description, including all omitted text, and rescan it for `Fixes`, `Closes`, `Resolves`, or `Ref` immediately adjacent to `ENG-<number>` or `#<number>` outside the top ticket line. | Passed checks: 5 passed | Check name | Status | Explanation | | :----------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits with the valid fix(docker) prefix, a lowercase descriptive summary, no trailing period, and an allowed ENG-2484 reference. | | Description check | | The description includes the required ticket reference, change rationale, affected locations, breaking-change assessment, migration details, test commands, coverage, open gaps, risks, and AI metadata.… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves `- [ ] This PR contains breaking changes` unticked. The reviewed diff changes only Docker update logic, tests, and documentation. It does not change an API shape, route, webh… | Full details: Description check **Explanation** The description includes the required ticket reference, change rationale, affected locations, breaking-change assessment, migration details, test commands, coverage, open gaps, risks, and AI metadata. It is complete and directly related to the changes. Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves `- [ ] This PR contains breaking changes` unticked. The reviewed diff changes only Docker update logic, tests, and documentation. It does not change an API shape, route, webhook payload or HTTP status; add or remove an environment/config key; add a manual database migration; or change the listed public SDK surfaces. The diff therefore contains no breaking change under this check. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table includes the changed update behaviors and observed outcomes, but the row “A stopped update leaves the Compose file untouched” is marked `unit (mutation)` without naming the mutated `file:line`. The row only says “moving the migration back above the AuthZed gate turns it red.” The changed call is at `docker/formbricks.sh:1342`, so the mutation is identifiable in the diff but not stated in the PR description as required. **Resolution** Update that Coverage row to name the mutation target, for example: `unit (mutation: move docker/formbricks.sh:1342, migrate_legacy_valkey_image, above the AuthZed gates)`; retain the observed outcome that the test turns red and rerun the named Vitest suite. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The visible description contains only the allowed top line `Ref ENG-2484`; other visible issue references such as `#9379` have no magic word. However, the supplied description is explicitly marked truncated, and no full PR description is available in the reviewed Git refs or checkout. A second magic-word pair in the omitted text cannot be ruled out. **Resolution** Provide the complete raw PR description, including all omitted text, and rescan it for `Fixes`, `Closes`, `Resolves`, or `Ref` immediately adjacent to `ENG-&lt;number&gt;` or `#&lt;number&gt;` outside the top ticket line. Comment `@the reviewer help` to get the list of available commands.
#9606
2026-10-20 16:04
cubic
inline · P2
docker/formbricks.sh:1342defect-functional
The later `authzed-ops upgrade check` can fail after this rewrite, leaving `docker-compose.yml` changed on a stopped update. Preserve or restore the original file when that gate fails so a failed readiness check does not leave the operator’s Compose file modified.
#9606
2026-10-20 16:04
cubic
review-body
—summary
**1 issue found** across 5 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9606
2026-10-20 16:06
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge; no actionable findings remain. Reviews (1) · Last reviewed commit: "fix(docker): warn instead of blocking wh..." · Reviewed by the reviewer
#9609
2026-10-20 16:08
coderabbit
issue-comment
—summary
### Walkthrough This pull request adds the 6.0-to-6.1 online upgrade path with persistent checkpoints, phased migrations, Kubernetes coordination, queue handoff, projection verification, and deployment templates. It enforces stored survey visibility during readiness repair and adds versioned, batched authorization. It preserves legacy embedded-data columns for compatibility. It also adds readiness checks, shutdown handling, migration index management, release assets, integration tests, and CI service restoration. Priority: ➖ Normal Merge Risk: 🟠 High · up to 83580 Several previously released database migrations were edited in place, so existing installations can fail to migrate on upgra
… the rest (8356 characters)de. The new online upgrade path cannot complete finalization while any survey exists, and its traffic-drain check can stall on busy clusters. The new container entry point can also break SAML connection preloading. Resolve these issues before merging. Security architecture risk: 🟠 High · up to 83580 Online finalization can remain blocked after the upgrade becomes forward-only, preventing normal completion and retirement of temporary administrative access. The mode is opt-in, and the authorization changes strengthen enforcement of stored survey restrictions. **Retained concerns** - **High · reliability · observed:** The new activation verifier treats any reconciliation update as projection drift. The existing helper always emits a touch for each expected relationship, and every survey has a workspace relationship. Consequently, an unchanged survey blocks finalization even when its graph is correct; reconciliation and ordinary retries cannot resolve this. The failure occurs after the forward-only checkpoint and prevents activation, surge cleanup, and temporary-access release. This is an introduced lifecycle failure with rollback and privileged-resource retirement consequences, not an observed authorization bypass. Security review details **Security Blast Radius** - _inferred_ — The lifecycle blocker affects the shared installation upgrade rather than one survey request. Temporary coordinator authority includes namespace deployment creation and pod patching, alongside named workload and service changes. This authority belongs to the deployment coordinator; no public route granting it to survey users was established. **Trust Boundaries and Controls** - _observed_ — Webhook delivery loads the destination and signing secret within the job workspace, rechecks subscription and current survey visibility on each attempt, and only then invokes the signed network request. The PR removes the previous readiness-based visibility bypass. - _observed_ — The traced workflow-email path checks survey workspace, response-to-survey ownership, current effective visibility, and literal-recipient workspace authorization before rendering and transport. Visibility and membership changes after context loading remain a pre-existing timing window, not an exposure introduced by this PR. **Resilience and Maintainability Implications** - _observed_ — Temporary access is attached to the finalization Job for garbage collection only after successful completion. Because projection verification blocks earlier, the normal access-retirement path is not reached. Verification still fails closed: the identified defect does not itself enable restricted reads or new-release producers. **Hardening Proposals** - _proposed_ — Consider binding checkpoint mutations to the owning database session or a fencing generation so an in-flight write cannot outlive advisory-lock ownership. Normal concurrency is already serialized; this proposal addresses interruption and failover rather than an established authorization bypass. Pre-merge checks: 4 · 1 · 2 Failed checks: 1 warning · 2 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Breaking Changes Match The Diff | | The Breaking changes checkbox is unticked, but the diff changes an existing API response value. In `apps/web/lib/survey/visibility/access.ts:22-25`, `getReportedVisibility` now always returns `getEffe… | Tick `- [x] This PR contains breaking changes` in the PR description and document the changed v3 survey `visibility` response value. | | Only The Ticket Line Carries A Magic Word | | The supplied PR description is truncated after `Conc`. The repository and reviewed Git refs contain no copy of the full description. The visible portion shows `No ticket supplied.` and no detectable `… | Provide the complete raw PR description, including the text after `Conc`, so all magic-word and issue-reference pairs can be scanned. | | Coverage Accounts For The Whole Diff | | The description is truncated inside the folded “Review regression coverage and reruns” section, so the full QA evidence is unavailable. The diff changes product behavior in health readiness, online-up… | Provide the complete PR description, including all folded Coverage rows, the remaining Open gaps content, and any screenshot or screencast attachments. Recheck that each changed behavior has a verification row or an explicit gap. | Passed checks: 4 passed | Check name | Status | Explanation | | :------------------------ | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title uses the required Conventional Commits format and clearly describes the guarded online 6.1 upgrade change. | | Description check | | The description includes the required rationale, review links, breaking-change status, migration and environment notes, testing coverage, open gaps, risks, and agent note. It is substantially over the… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | Full details: Description check **Explanation** The description includes the required rationale, review links, breaking-change status, migration and environment notes, testing coverage, open gaps, risks, and agent note. It is substantially over the template’s recommended length, but the required information is present and relevant. Full details: Breaking Changes Match The Diff **Explanation** The Breaking changes checkbox is unticked, but the diff changes an existing API response value. In `apps/web/lib/survey/visibility/access.ts:22-25`, `getReportedVisibility` now always returns `getEffectiveVisibility(row)` instead of returning `"workspace"` when readiness is unavailable. This function supplies the `visibility` field in v3 survey representations, so existing responses can emit `"restricted"` instead of `"workspace"`. **Resolution** Tick `- [x] This PR contains breaking changes` in the PR description and document the changed v3 survey `visibility` response value. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The supplied PR description is truncated after `Conc`. The repository and reviewed Git refs contain no copy of the full description. The visible portion shows `No ticket supplied.` and no detectable `Fixes`, `Closes`, `Resolves`, or `Ref` pair, but the omitted text may contain one. **Resolution** Provide the complete raw PR description, including the text after `Conc`, so all magic-word and issue-reference pairs can be scanned. Full details: Coverage Accounts For The Whole Diff **Explanation** The description is truncated inside the folded “Review regression coverage and reruns” section, so the full QA evidence is unavailable. The diff changes product behavior in health readiness, online-upgrade compatibility, migration phases, projection, queue pause delivery, Kubernetes orchestration, authorization, and survey visibility. The visible coverage rows and open gaps do not establish whether the omitted text accounts for every one of these behaviors, nor whether any required UI screenshot is attached. **Resolution** Provide the complete PR description, including all folded Coverage rows, the remaining Open gaps content, and any screenshot or screencast attachments. Recheck that each changed behavior has a verification row or an explicit gap. Comment `@the reviewer help` to get the list of available commands.
#9609
2026-10-20 16:22
greptile
issue-comment
—summary
Confidence Score: 4/5 **[High impact]** The opt\-in upgrade reaches live deployments\, database migrations\, queued jobs\, and survey access decisions\. Changes are required before merging because custom-schema installations can fail normal maintenance startup; the separate atomicity concern is non-blocking for the current retry-safe migration. Findings 1. **Custom\-schema startup fails** 2. **Schema commits before history** Reviews (1) · Last reviewed commit: "feat: add guarded online 6.1 upgrades (#..." · Reviewed by the reviewer
#9609
2026-10-20 16:22
greptile
inline · P1
packages/database/src/scripts/online-indexes.ts:78defect-functional
**Custom-schema startup fails** If an installation selects its PostgreSQL schema with `?schema=customer` and leaves the session search path at its default, this raw `pg.Client` does not apply that selection. The index lookup checks `public`, and index creation fails because the application tables are in `customer`. This happens even when all required indexes already exist in the selected schema. The new index step also runs after maintenance migrations, so its failure prevents normal startup. Apply the selected schema consistently to this connection's catalog queries and index statements before merging. Artifacts **Complete executable custom-schema regression source** - The authored har
… the rest (1019 characters)ness creates a real PostgreSQL fixture and invokes the unmodified ensureOnlineIndexes function in control and candidate modes, making the validation reproducible. **Successful index check with explicit customer search path** - Executed the control against real PostgreSQL with an explicit customer search path and captured session settings, SQL, and index validity, showing that the function succeeds when schema selection is applied. **Failed index check with only the Prisma customer schema parameter** - Executed the candidate URL against the same fixture shape and captured the public session schema, attempted CREATE INDEX, and PostgreSQL error, confirming the schema-selection defect. **Maintenance failure propagation and repository state** - Captured the checked-out commit, unchanged tracked files, maintenance call path, CLI exit handling, startup failure handling, and database shutdown, documenting the startup consequence without claiming an application boot test. Ran code and verified through TREX
#9609
2026-10-20 16:22
greptile
inline · P2
packages/database/src/scripts/upgrade-session.ts:85defect-data
**Schema commits before history** During preparation from the supported 6.0 releases, the bundled matrix migration contains its own `BEGIN` and `COMMIT`. Executing it inside this wrapper commits the schema change before the migration-history insert. A failure between those operations therefore leaves the `matrix` enum value committed without its history record, despite the wrapper's stated atomicity guarantee. This is non-blocking for this particular migration: it is additive and retry-safe, and a retry successfully records history. The practical cost is an interrupted upgrade with schema and migration history temporarily out of sync. Handle bundled transaction boundaries before wrapping t
… the rest (1373 characters)he SQL, preserve released migration bytes and checksums, and cover the actual bundled migration in the atomicity regression test. Artifacts **Executed PostgreSQL reproduction harness** - The executed harness provisions disposable PostgreSQL, imports the actual wrapper, injects the post-SQL failure, and asserts rollback, restart durability, and retry outcomes. **PostgreSQL control without inner transaction boundaries** - Ran the actual wrapper with only the migration’s inner BEGIN and COMMIT removed; rollback removes the enum alteration and retry completes successfully. **PostgreSQL reproduction with exact bundled migration** - Ran the actual wrapper with unchanged bundled SQL and injected failure before history insertion; the enum survives PostgreSQL restart without history, confirming broken atomicity. **Executed source and unchanged production worktree** - Captured the reviewed head, wrapper, bundled SQL, preparation call path, harness source, and clean tracked-file diff; the reproduction used the requested head without production edits. **Initial control run with checksum expectation mismatch** - The initial control reproduced correct rollback and successful retry but failed the final verifier on its modified SQL checksum; this exposed a harness expectation corrected before the passing comparison runs. Ran code and verified through TREX
#9609
2026-10-20 16:22
cubic
inline · P1
authzed/upgrade/schema-6.0.zed:203defect-security
A cross-organization `WorkspaceTeam` grant lets members of the team's organization read the workspace without membership in the workspace's organization; the same gap affects `write` and `manage`. Gate each team-derived workspace permission by `workspace.organization->product_member` so invalid cross-tenant grants fail closed.
#9609
2026-10-20 16:22
cubic
inline · P1
apps/web/lib/upgrade/online/kubernetes.ts:443defect-functional
This checks only that discovered gateways are allowlisted; it never requires every configured proxy to match the selector, so a selector that omits an active Envoy lets drain skip it. Require the discovered gateway inventory to match `proxyResourceNames` before querying proxies.
#9609
2026-10-20 16:22
cubic
inline · P1
apps/web/lib/upgrade/online/kubernetes.ts:490defect-functional
This cluster-wide gauge also counts requests to the new live endpoints, so sustained traffic can keep it nonzero throughout the timeout and block handoff or cleanup. Measure activity only on withdrawn hosts or use a drain condition scoped to old endpoints.
#9609
2026-10-20 16:22
cubic
inline · P1
packages/database/src/scripts/upgrade-session.ts:85defect-data
This helper wraps every migration in a transaction, but `20260928120000_add_matrix_chart_type/migration.sql` contains its own `BEGIN`/`COMMIT`; its inner `COMMIT` closes the wrapper before `_prisma_migrations` is written. Make bundled SQL transaction-free when using this helper, or handle explicit-transaction migrations outside the wrapper so schema and history remain atomic.
#9609
2026-10-20 16:22
cubic
review-body
—summary
**31 issues found** across 163 files This PR changes a database schema or migrates data. Ultrareviews find 2.4x more serious bugs than standard reviews. Comment `@the reviewer-dev-ai ultrareview` to run one.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9609
2026-10-20 16:22
cubic
inline · P2
packages/database/src/scripts/online-indexes.test.ts:53tests
This rejects the advisory-lock query, not an index build, so the test never exercises cleanup after a failed build. Reject a `CREATE INDEX CONCURRENTLY` query instead. ```suggestion query.mockImplementation((sql: string) => { if (sql.includes("pg_try_advisory_lock")) return Promise.resolve({ rows: [{ acquired: true }] }); if (/^CREATE (UNIQUE )?INDEX CONCURRENTLY/.test(sql)) return Promise.reject(new Error("connection lost")); return Promise.resolve({ rows: [] }); }); ```
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/lib/authorization/source-scope.test.ts:58tests
This replacement drops the only `resolveAuthorizationScope` assertion for dashboards, leaving the dashboard-to-workspace mapping untested. Keep a dashboard case alongside the survey and response cases.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/instrumentation.test.ts:5tests
This mock replaces the new pause-delivery registration with a no-op, and no test asserts it is called; assert it during normal Node startup so a missing registration cannot pass unnoticed.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/lib/upgrade/online/history.test.ts:15tests
The checkpointed-additions test covers schema history only, so regressions in allowing target data migrations only after preparation go undetected. Add an applied `new-data` row and assert it is rejected without intent and accepted with intent.
#9609
2026-10-20 16:22
cubic
inline · P2
.github/workflows/integration-tests.yml:147defect-functional
The harness flag only proves the Better Auth fixture exists; older PR heads can have that fixture but lack these new test files, causing Vitest to fail with no matching files and blocking their checks. Guard each invocation on its test file being present.
#9609
2026-10-20 16:22
cubic
inline · P2
packages/jobs/src/upgrade.integration.test.ts:21defect-performance
If opening the upgrade queue fails, execution exits before the `try` and leaves the already-created worker and Redis clients unclosed. Move setup under the cleanup guard and close each resource that was successfully created.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/lib/survey/service.test.ts:902tests
These new assertions fail because `updateSurveyInternal` intentionally removes `variables` and `hiddenFields` before calling Prisma and only reconciles them to embedded-data rows. Keep the tests aligned with that behavior, or add the corresponding service/schema support before asserting legacy-column writes.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/Dockerfile:121defect-functional
This command generates a different Next `deploymentId` for each platform build, even though the Docker action publishes both architectures as one release. Requests routed between architectures can therefore hit deployment-mismatch handling; generate the ID once and pass it to both platform builds.
#9609
2026-10-20 16:22
cubic
inline · P2
docs/self-hosting/configuration/online-upgrade.mdx:172docs
Preparation can fail during preflight or database migrations before `platform.handoff` creates surge Pods, so this overstates available capacity. Limit the claim to failures after surge creation.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/lib/survey/service.custom-css.test.ts:63tests
`lastUpdateData` now reads the first call, so an app-survey update that writes its segment before the final transactional save makes assertions inspect the segment mutation instead of the saved payload. Select the last call with `at(-1)`. ```suggestion (vi.mocked(prisma.survey.update).mock.calls.at(-1)?.[0].data ?? {}) as Record<string, unknown>; ```
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/lib/jobs/recurring-registrations.ts:92defect-functional
This flag only prevents a new scheduler upsert; installations already have the recurring scheduler, which remains active when the upsert is skipped, and its handler is still registered. Guard execution until activation or explicitly disable the existing scheduler.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/lib/upgrade/online/queue-handoff.test.ts:147tests
This test never verifies that `assertDrained` checks the coordinator lock, so deleting that lock check still passes when queue activity triggers the rejection. Assert that the callback invokes `assertLock` before checking queue activity. ```suggestion const lockChecksBefore = vi.mocked(deps.assertLock).mock.calls.length; try { await assertDrained(); } finally { expect(deps.assertLock).toHaveBeenCalledTimes(lockChecksBefore + 1); } ```
#9609
2026-10-20 16:22
cubic
inline · P2
packages/database/migration/20260909120001_verify_response_keyset_indexes_valid/migration.sql:2docs
This makes the migration a no-op, but the database README still says this file contains the invalid-index guard. Update that guidance to point to runner-managed index validation so operators are not directed to obsolete behavior.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/vite.authzed-cli.config.mts:36defect-functional
Turbo does not hash these `authzed/upgrade` assets into the web build, so later edits to the source migration/image catalogs or 6.0 schema can restore a CLI bundle with stale upgrade metadata. Add these files or their directory to the web build's cache inputs.
#9609
2026-10-20 16:22
cubic
inline · P2
charts/formbricks/templates/online-upgrade.yaml:19defect-functional
Long but valid chart names can make the generated Job names exceed Kubernetes' 63-character limit, causing online upgrades to fail when applying hooks. Truncate the base used for online resources to leave room for the longest suffix.
#9609
2026-10-20 16:22
cubic
inline · P2
charts/formbricks/templates/online-upgrade.yaml:23defect-functional
When `cube.enabled=false`, the chart does not render this Deployment, but the coordinator still requires it and fails preflight even when an external Cube is configured. Only append the built-in Cube dependency when enabled, or reject this mode explicitly at render time.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/lib/upgrade/online/projection.ts:87defect-functional
A failed reconciliation is reported as `stable_projection_drift`, so operators cannot distinguish projection failure from repaired drift awaiting re-verification. Check the returned status and report `survey_projection_failed` when it is not projected.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/lib/upgrade/online/queue-handoff.ts:39defect-functional
This records `queuePausedAt` before Redis is paused, but status derives `jobs.paused` from that timestamp alone. A failed or interrupted `queue.pause()` therefore reports the queue as paused while workers can still consume jobs; represent the pause intent separately from the confirmed paused state.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/lib/upgrade/online/coordinator.ts:125defect-concurrency
The coordinator can activate projection readiness after losing its lock during consumer verification. Recheck the lock immediately before `activate()`; the later check only detects lock loss after the readiness write. ```suggestion await deps.assertLock(); await deps.activate(); ```
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/scripts/docker/server.js:70defect-functional
This catch discards the initialization error, so a rejected `getRequestHandlers` startup can exit with status 1 without its cause being logged. Log the error before starting shutdown.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/modules/ee/analysis/charts/lib/charts.ts:34defect-functional
Partial updates to an existing matrix chart bypass this guard because `type` is optional. Fetch the chart first, then reject when either `data.type` or the stored `chart.type` is `"matrix"` before applying query/config changes.
#9609
2026-10-20 16:22
cubic
inline · P2
charts/formbricks/templates/online-upgrade.yaml:9defect-functional
`sessionAffinity: true` does not ensure the coordinator receives `MIGRATE_DATABASE_URL`, so Helm accepts configurations whose prepare hook immediately returns `session_affine_migration_connection_required`. Validate the supported `deployment.env`/`envFrom` injection path or fail this configuration during rendering.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/scripts/docker/server.js:67defect-functional
Directly registering `handlers.upgradeHandler` drops Next's rejection handling, so a failed WebSocket upgrade can leave its socket open and surface as an unhandled rejection. Wrap the call in `try/catch` and destroy the socket on failure, matching Next's server wrapper.
#9609
2026-10-20 16:22
cubic
inline · P2
apps/web/instrumentation-jobs.ts:100defect-concurrency
The draining guard does not stop an in-flight recurring-registration promise, and shutdown never waits for that promise. Re-check draining after each awaited registration step and await or cancel the registration before exit so SIGTERM cannot persist schedulers after shutdown starts.
#9609
2026-10-20 16:22
cubic
inline · P3
apps/web/app/api/v3/surveys/authorization.ts:44docs
This block now calls `can` even when the readiness marker is off, so the comment's claim that those deployments skip the second check is false. Update it to describe the readiness-aware fallback.
#9609
2026-10-20 16:22
cubic
inline · P3
apps/web/modules/response-pipeline/lib/process-response-pipeline-job.ts:217defect-functional
Inline delivery logs are attributed to `formbricks-jobs`, but the actual queue is `background-jobs`; this misattributes failures during triage. Pass `logContext.queueName`. ```suggestion queueName: logContext.queueName, ```
#9609
2026-10-20 16:22
cubic
inline · P3
apps/web/modules/survey/visibility/lib/gate.ts:18docs
The UI gate now reports enforcement even when `gates.ready` is false, but the surrounding comments still say enforcement follows readiness. Update those comments to document always-on enforcement so future changes do not restore the old behavior.
#9609
2026-10-20 16:23
coderabbit
inline · P1
apps/web/lib/upgrade/online/kubernetes.ts:490defect-functional
**Use host-level request accounting for the ingress drain gate.** `cluster.<name>.upstream_rq_active` is a cluster-level Envoy gauge. It includes active requests for all hosts in the configured cluster, including the target surge pods. The gate first confirms that no old host remains in `host_statuses`, then requires the cluster-wide gauge to equal `0`. Any active request on a target host can therefore keep `wait()` in `handoff_pending` until the timeout. The same check also runs during `cleanup()` for the surge pods. Use per-host `rq_active` data from `clusters?format=json`, or another check that proves only the withdrawn hosts have drained. Do not use the cluster-wide total.
#9609
2026-10-20 16:23
coderabbit
inline · P3
apps/web/lib/upgrade/online/projection.test.ts:35tests
**Cover the survey-scan path with a realistic relationship result.** The fresh-live-grants case scans no surveys, so it never calls `diffSurveyRelationships`. The only other projection test uses `[{}]`, and the helper test does not invoke `verifyOnlineProjection`. Add a row-backed verification case that uses the real helper or a correctly shaped relationship update result.
#9609
2026-10-20 16:23
coderabbit
inline · P3
apps/web/lib/upgrade/online/projection.ts:76defect-performance
**Verification makes one SpiceDB read and one lock query per survey, one at a time.** Each pass calls `assertLock()` (a PostgreSQL round trip) and `readAllRelationships` (a SpiceDB round trip) once per survey, in sequence. With 100k surveys and about 5 ms per round trip, one pass takes about 15–20 minutes. The coordinator Job has a 3600 s `activeDeadlineSeconds`. Any drift retry restarts the full scan. Run the reads for a page with bounded concurrency, or use a filter that returns all relationships for the page. Call `assertLock` once per page, not once per row. As per path instructions: "N+1 work: a query, AI call or cross-service fetch issued inside a `for`/`map`/`Promise.all` over rows.
… the rest (30 characters)" _Source: Path instructions_
#9609
2026-10-20 16:23
coderabbit
inline · P1
apps/web/lib/upgrade/online/projection.ts:89defect-functional
**Every stable survey counts as drift, so finalization never activates.** In `apps/web/lib/authzed/survey.ts:69-81`, `diffSurveyRelationships` returns `[...expected.map(touch), ...deletes]`. `expectedSurveyRelationships` always contains the `workspace` edge. The result therefore always has length ≥ 1, even when SpiceDB already matches PostgreSQL exactly. The consequence for the first survey in a correctly projected install: 1. `findUnique` returns the same version and facts as the row, so the row counts as stable. 2. The code calls `reconcileSurveyRelationships` and then throws `stable_projection_drift`. 3. Every retry starts again at the first survey, so `verifyProjection` cannot succeed
… the rest (1242 characters)while any survey exists. `forwardOnlyAt` is already recorded at `coordinator.ts:114`. The installation is therefore stuck after the rollback boundary, and survey visibility never activates. The unit test does not catch this because it mocks `diffSurveyRelationships` to `[]`. Count only real differences: deletions, plus expected edges that are missing from `relationships`. 🐛 Proposed fix ```diff --- "a/apps/web/lib/upgrade/online/projection.ts" +++ "b/apps/web/lib/upgrade/online/projection.ts" @@ -74,7 +74,11 @@ resourceId: row.id, resourceType: "survey", }); - if (diffSurveyRelationships(relationships, expectedSurveyRelationships(row)).length) { + const currentKeys = new Set(relationships.map((r) => JSON.stringify(r))); + const drift = diffSurveyRelationships(relationships, expectedSurveyRelationships(row)).some( + (update) => update.operation === "delete" || !currentKeys.has(JSON.stringify(update.relationship)) + ); + if (drift) { const current = await prisma.survey.findUnique({ where: { id: row.id }, select }); if ( current && ``` Use the module's `relationshipKey` for the comparison instead of `JSON.stringify`, if it is exported.
#9609
2026-10-20 16:23
coderabbit
inline · P2
apps/web/lib/upgrade/online/runtime.ts:220defect-functional
**Preserve repeated handoff verification without overwriting the completed phase.** A completed handoff enters this branch because `queueResumedAt` is set. The runtime writes `phase: "handoff"`, then `handoffJobs` verifies that source consumers remain absent. Because `queueResumedAt` is already set, that branch does not write `phase: "rollout"`, so status remains `handoff`. Keep the handoff calls. Only skip the phase overwrite: Suggested fix ```diff if (!checkpoint?.queuePausedAt || checkpoint.queueResumedAt) { - await save({ phase: "handoff" }); + if (!checkpoint?.completedPhases.includes("consumers-retired")) { + await save({ phase: "handoff" }); +
… the rest (749 characters)} await platform.handoff(lock.assertLock); } ``` 📝 Committable suggestion > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion if (command === "prepare" && platform.handoff && !(await read())?.activatedAt) { const checkpoint = await read(); if (!checkpoint?.queuePausedAt || checkpoint.queueResumedAt) { if (!checkpoint?.completedPhases.includes("consumers-retired")) { await save({ phase: "handoff" }); } await platform.handoff(lock.assertLock); } ```
#9609
2026-10-20 16:23
coderabbit
inline · P1
apps/web/scripts/docker/server.js:13defect-functional
**Set the standalone server’s working directory before initializing Next.** `next-start.sh` already exports `NODE_ENV=production`, so that part of the generated bootstrap is covered. The runner starts in `/home/nextjs`, but the copied adapter’s `__dirname` is `/home/nextjs/apps/web`. `SAML_XML_DIR` is `./saml-connection`, and the SAML preloader reads that path relative to the working directory. Without `process.chdir(__dirname)`, it can read `/home/nextjs/saml-connection` instead of the mounted `/home/nextjs/apps/web/saml-connection`. Proposed fix ```diff --- "a/apps/web/scripts/docker/server.js" +++ "b/apps/web/scripts/docker/server.js" @@ -6,8 +6,9 @@ const { join } = require("node:pat
… the rest (1279 characters)h"); const { shutdown } = require("./shutdown.js"); const dir = __dirname; +process.chdir(dir); const config = JSON.parse(readFileSync(join(dir, ".next/required-server-files.json"), "utf8")).config; // eslint-disable-next-line turbo/no-undeclared-env-vars -- Next's generated standalone entry point sets this at runtime too. process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(config); const { getRequestHandlers } = require("next/dist/server/lib/start-server"); ``` Update the existing test process stub to include `chdir: vi.fn()`. 📝 Committable suggestion > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion const dir = __dirname; process.chdir(dir); const config = JSON.parse(readFileSync(join(dir, ".next/required-server-files.json"), "utf8")).config; // eslint-disable-next-line turbo/no-undeclared-env-vars -- Next's generated standalone entry point sets this at runtime too. process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(config); const { getRequestHandlers } = require("next/dist/server/lib/start-server"); ```
#9609
2026-10-20 16:23
coderabbit
inline · P2
docs/self-hosting/configuration/online-upgrade.mdx:5docs
**Add the page to the Mintlify navigation.** `docs/self-hosting/configuration/online-upgrade.mdx` is new, but `docs/docs.json` does not reference it. The repository rule requires every new MDX page to be added to the navigation, so this page will remain unlisted. Suggested fix ```diff "self-hosting/configuration/ai-features", "self-hosting/configuration/authzed-operations", + "self-hosting/configuration/online-upgrade", "self-hosting/configuration/rate-limiting", ```
#9609
2026-10-20 16:23
coderabbit
inline · P1
packages/database/migration/20260909120000_response_keyset_index_tiebreakers/migration.sql:2defect-data
**Do not rewrite applied migrations.** All five cited migration files existed before this PR and are modified in the base-to-head diff. This violates the append-only migration requirement. Databases that applied the earlier contents can fail migration deployment on checksum validation, and the removed guard will not run again for those databases. Restore the historical migration files. Do not move the removal into a later migration, because a later migration cannot prevent an earlier migration from executing on a fresh database. If the online runner must own these indexes, update the runner or its fresh-database path so it supports both fresh and already-migrated databases without changing
… the rest (47 characters) historical files. _Source: Path instructions_
#9609
2026-10-20 16:23
coderabbit
review-body
—summary
**Actionable comments posted: 8**
#9600
2026-10-20 17:14
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The OAuth state cookie is configured with a 10-minute lifetime. Integration tests cover a seven-minute SAML sign-in delay and verify state validation immediately before and after expiration. The observability documentation describes concurrent sign-ins that overwrite the state cookie and slow sign-ins that expire with `state_mismatch`. Priority: ➖ Normal **Severity of issue fixed:** Medium Merge Risk: ⚪ Minimal · up to 7a7e1 This change keeps the SSO state cookie valid for the same 10 minutes as the state it protects, so slower sign-in callbacks no longer fail with a state mismatch. The tests cover a seven-m
… the rest (9064 characters)inute delay and the expiry boundary. No actionable merge-blocking risk was identified. Security architecture risk: ⚪ Minimal · up to 7a7e1 The change aligns cookie retention with the existing ten-minute sign-in window without extending server-side state validity. Signed browser binding and cookie protections remain intact. No material security risk introduced or worsened by this change was identified. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The configuration applies across OAuth flows using this application instance’s database-backed state-cookie path, rather than to one test provider or tenant. Its direct effect is retaining pending browser-binding state for five additional minutes; session lifetime and automatic account-linking policy are unchanged. **Trust Boundaries and Controls** - _observed_ — Provider-returned state and code remain untrusted callback inputs. State must match the initiating browser’s signed cookie and valid stored payload before token exchange. Reaching state validation does not itself grant a session: provider authorization-code validation and user resolution still precede session-cookie issuance. **Resilience and Maintainability Implications** - _observed_ — For a matching callback, state-cookie expiry and verification deletion occur before provider-denial handling or token exchange. Thus a completed deletion prevents sequential reuse even when downstream exchange fails; the user must start a fresh flow. A state-cookie mismatch is rejected before deleting the pending verification. - _inferred_ — The existing state parser performs verification lookup and deletion separately, so concurrent matching callbacks may both pass state parsing. This is not an atomic single-use guarantee. It predates the PR and is not shown to be materially worsened: a caller retaining the signed cookie could already submit it during the same ten-minute server-validity window, since signature verification contains no browser Max-Age check. Passing this race alone does not establish unauthorized session issuance. Pre-merge checks: 4 · 3 Failed checks: 3 warnings | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Linked Issues check | | Issue `#11170` requires more than the local cookie override. It requires a configurable `account.stateExpiresIn` option, one lifetime derived from the payload for the cookie and verification row, consis… | Implement the `#11170` requirements in the relevant Better Auth code and tests. Add and document `account.stateExpiresIn`, derive the cookie and verification-row lifetimes from the enforced state expiry, apply the option to every listed state… | | Breaking Changes Match The Diff | | The Breaking changes checkbox is unticked, but the diff changes an emitted HTTP response value. In `apps/web/modules/auth/lib/auth.ts:413`, `advanced.cookies.state.attributes.maxAge` sets the OAuth st… | Tick `- [x] This PR contains breaking changes` in the PR description and document the OAuth state cookie `Max-Age` change. | | Coverage Accounts For The Whole Diff | | The description lists coverage for the changed OAuth cookie lifetime, expiry boundary, and cookie attributes. However, both rows marked `mutation` do not name the required `file:line` edit location. T… | Update both mutation rows in the Coverage table to name `apps/web/modules/auth/lib/auth.ts:413` as the mutation target, including the specific `cookies: { state: ... }` override. Keep the existing mutation and observed-outcome details. | Passed checks: 4 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits syntax, uses an allowed type and optional scope, describes the OAuth state-cookie change, uses lower-case wording without a period, and includes a valid ENG-3314… | | Description check | | The description includes the required ticket reference, change summary, review links, breaking-change status, migration notes, test command, coverage, open gaps, risks, and AI metadata. It clearly exp… | | Out of Scope Changes check | | The `auth.ts` override directly addresses `#11170`'s cookie/state lifetime mismatch. The integration test verifies delayed callback validation, expiry alignment, and cookie security attributes. The obse… | | Only The Ticket Line Carries A Magic Word | | PASS: The only magic-word/ticket pair in the PR description is the top line “Fixes ENG-3314”. The later text “upstream fix better-auth/better-auth#11170 is open” uses lowercase “fix” and does not matc… | Full details: Title check **Explanation** The title follows Conventional Commits syntax, uses an allowed type and optional scope, describes the OAuth state-cookie change, uses lower-case wording without a period, and includes a valid ENG-3314 reference. Full details: Description check **Explanation** The description includes the required ticket reference, change summary, review links, breaking-change status, migration notes, test command, coverage, open gaps, risks, and AI metadata. It clearly explains the OAuth state-cookie lifetime fix and its validation. Full details: Out of Scope Changes check **Explanation** The `auth.ts` override directly addresses `#11170`'s cookie/state lifetime mismatch. The integration test verifies delayed callback validation, expiry alignment, and cookie security attributes. The observability comment documents the remaining state-cookie behavior after the fix. These changes stay within the linked issue's scope. Full details: Only The Ticket Line Carries A Magic Word **Explanation** PASS: The only magic-word/ticket pair in the PR description is the top line “Fixes ENG-3314”. The later text “upstream fix better-auth/better-auth#11170 is open” uses lowercase “fix” and does not match the specified magic words. Full details: Linked Issues check **Explanation** Issue `#11170` requires more than the local cookie override. It requires a configurable `account.stateExpiresIn` option, one lifetime derived from the payload for the cookie and verification row, consistent use across social sign-in, account linking, OAuth callbacks, oauth-proxy, generic OAuth, and SSO/SAML, plus documentation and error-page updates. The complete PR diff changes only the web application's `advanced.cookies.state` setting, related observability text, and an integration test. It does not implement those Better Auth changes or the requested broader tests. **Resolution** Implement the `#11170` requirements in the relevant Better Auth code and tests. Add and document `account.stateExpiresIn`, derive the cookie and verification-row lifetimes from the enforced state expiry, apply the option to every listed state-minting flow, and update the `state_mismatch` error page. Full details: Breaking Changes Match The Diff **Explanation** The Breaking changes checkbox is unticked, but the diff changes an emitted HTTP response value. In `apps/web/modules/auth/lib/auth.ts:413`, `advanced.cookies.state.attributes.maxAge` sets the OAuth state cookie to 600 seconds instead of Better Auth's prior 300-second value. This changes the `Set-Cookie` response emitted by the sign-in route and matches the check's breaking-change condition for a changed emitted value. **Resolution** Tick `- [x] This PR contains breaking changes` in the PR description and document the OAuth state cookie `Max-Age` change. Full details: Coverage Accounts For The Whole Diff **Explanation** The description lists coverage for the changed OAuth cookie lifetime, expiry boundary, and cookie attributes. However, both rows marked `mutation` do not name the required `file:line` edit location. They say only `auth.ts` and describe the override. The changed override is at `apps/web/modules/auth/lib/auth.ts:413`. **Resolution** Update both mutation rows in the Coverage table to name `apps/web/modules/auth/lib/auth.ts:413` as the mutation target, including the specific `cookies: { state: ... }` override. Keep the existing mutation and observed-outcome details. Comment `@the reviewer help` to get the list of available commands.
#9600
2026-10-20 17:21
cubic
review-body
—summary
**No issues found** across 3 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9600
2026-10-20 17:23
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge within the reviewed scope; the authentication-handler integration tests passed with real database and cache services. Reviews (1) · Last reviewed commit: "fix(auth): keep the OAuth state cookie a..." · Reviewed by the reviewer
#9608
2026-10-20 17:24
coderabbit
issue-comment
—summary
### Walkthrough Both stylesheets now define unitless line-height tokens for leading 4, 5, and 6. A regression test compiles the stylesheets and checks for the expected ratios and absence of length-based line-height declarations. Priority: ⬇️ Low Merge Risk: 🔵 Low · up to 0db0c The line-height change is mergeable with follow-up on the regression test. A future compiler change could make the test fail even if the rendered line height remains correct. Pre-merge checks: 5 · 1 · 1 Failed checks: 1 warning · 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :------------------------------------------------------------------------
… the rest (5569 characters)--------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Coverage Accounts For The Whole Diff | | The coverage table does not account for the standalone survey-ui behavior changed by the diff. `packages/survey-ui/src/styles/globals.css` changes `leading-4/5/6`, which affects standalone `@formbrick… | Add a Coverage row with a verification method and observed outcome for standalone `@formbricks/survey-ui` labels, or move this unverified behavior to Open gaps with the reason it was not checked. | | Only The Ticket Line Carries A Magic Word | | The visible description contains the allowed ticket line `Ref ENG-3427` and no other matching magic-word pair. The supplied description is explicitly truncated, and the repository refs contain no full… | Provide the complete raw pull-request description, or rerun the check with access to the full PR body. Then scan every occurrence of `Fixes`, `Closes`, `Resolves`, or `Ref` followed by `ENG-<number>` or `#<number>`; only the top `Ref ENG-34… | Passed checks: 5 passed | Check name | Status | Explanation | | :----------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits with the fix(surveys) prefix, clearly describes the unitless line-height change, and has no trailing period. | | Description check | | The description includes the ticket reference, change summary, review links, breaking-change declaration, migration status, test command, coverage, open gaps, risks, and AI note. It is specific to the… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description unticks the breaking-changes checkbox and states that no breaking changes are present. The reviewed diff only adds unitless Tailwind line-height theme values and a regression test i… | Full details: Description check **Explanation** The description includes the ticket reference, change summary, review links, breaking-change declaration, migration status, test command, coverage, open gaps, risks, and AI note. It is specific to the changeset and complete enough for review. Full details: Breaking Changes Match The Diff **Explanation** The PR description unticks the breaking-changes checkbox and states that no breaking changes are present. The reviewed diff only adds unitless Tailwind line-height theme values and a regression test in `packages/survey-ui` and `packages/surveys`. It does not rename or retype an API field, change a route or webhook, change an environment/config key or default, add a manual database migration, or change an exported SDK signature. Full details: Coverage Accounts For The Whole Diff **Explanation** The coverage table does not account for the standalone survey-ui behavior changed by the diff. `packages/survey-ui/src/styles/globals.css` changes `leading-4/5/6`, which affects standalone `@formbricks/survey-ui` labels. The description acknowledges the 24px/18px to 21px/18px change only under Risks. Its Coverage rows verify renderer CSS, iOS rendering, and link/widget states, while Open gaps lists only apps/web and Android checks. **Resolution** Add a Coverage row with a verification method and observed outcome for standalone `@formbricks/survey-ui` labels, or move this unverified behavior to Open gaps with the reason it was not checked. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The visible description contains the allowed ticket line `Ref ENG-3427` and no other matching magic-word pair. The supplied description is explicitly truncated, and the repository refs contain no full PR description, so the whole-body scan cannot establish that no additional pair exists. **Resolution** Provide the complete raw pull-request description, or rerun the check with access to the full PR body. Then scan every occurrence of `Fixes`, `Closes`, `Resolves`, or `Ref` followed by `ENG-&lt;number&gt;` or `#&lt;number&gt;`; only the top `Ref ENG-3427` pair is allowed. > [!WARNING] > Some tools did not complete. Review the errors below. > > > 🔧 Biome (2.5.14) > > > > > packages/survey-ui/src/styles/globals.css > > > > File contains syntax errors that prevent linting: Line 26: Tailwind-specific syntax is disabled.; Line 28: Tailwind-specific syntax is disabled.; Line 37: Tailwind-specific syntax is disabled. > > > > > > > packages/surveys/src/styles/global.css > > > > File contains syntax errors that prevent linting: Line 14: Tailwind-specific syntax is disabled.; Line 16: Tailwind-specific syntax is disabled.; Line 26: Tailwind-specific syntax is disabled.; Line 74: Tailwind-specific syntax is disabled. > > > > > > > > Comment `@the reviewer help` to get the list of available commands.
#9608
2026-10-20 17:31
coderabbit
inline · P3
packages/surveys/src/lib/unitless-line-heights.test.ts:80tests
**Assert both line-height properties by name.** The array assertion requires exactly two declarations. Reordering alone does not affect this case because both expected values are equal, but declaration-count changes can fail the test even when both properties remain correct. Tailwind text utilities consume `--tw-leading`, so retain checks for both properties. Use a property map instead. The proposed type change must also update the local `found` declaration. Suggested fix ```diff -const leadingValues = (root: postcss.Root): Record<string, { expected: number; values: number[] }> => { - const found: Record<string, { expected: number; values: number[] }> = {}; +const leadingValues = ( + ro
… the rest (872 characters)ot: postcss.Root +): Record<string, { expected: number; values: Record<string, number> }> => { + const found: Record<string, { expected: number; values: Record<string, number> }> = {}; root.walkDecls(/^(line-height|--tw-leading)$/, (decl) => { const step = /\.leading-(\d+)(?![\w-])/.exec(selectorOf(decl))?.[1]; if (!step) return; - found[`leading-${step}`] ??= { expected: Number(step) / 4, values: [] }; - found[`leading-${step}`].values.push(Number(decl.value)); + found[`leading-${step}`] ??= { expected: Number(step) / 4, values: {} }; + found[`leading-${step}`].values[decl.prop] = Number(decl.value); }); return found; }; ``` ```diff - expect(values, utility).toEqual([expected, expected]); + expect(values["line-height"], utility).toBe(expected); + expect(values["--tw-leading"], utility).toBe(expected); ```
#9608
2026-10-20 17:31
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9608
2026-10-20 17:34
cubic
review-body
—summary
**2 issues found** across 3 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9608
2026-10-20 17:34
cubic
inline · P2
packages/surveys/src/styles/global.css:26defect-functional
Unitless `leading-6` is inherited as a factor, so rich-text headings inside `.htmlbody` now get taller line boxes at default size. The editor's `.fb-editor-heading-h1` (25px) and `-h2` (20px) previously inherited the parent's absolute 21px/24px line-height and now compute 1.5× their own font size. The PR's 'default size unchanged' claim only holds for the element carrying the class, so either set an explicit line-height on the heading classes or confirm the new spacing is intended.
#9608
2026-10-20 17:34
cubic
inline · P3
packages/surveys/src/lib/unitless-line-heights.test.ts:80tests
Track the generated values by property name and assert both `line-height` and `--tw-leading` instead of requiring exactly two declarations; a declaration-count change can fail this test while both properties remain correct.
#9608
2026-10-20 17:39
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge based on the tested scope; no blocking issues were identified. Reviews (1) · Last reviewed commit: "fix(surveys): keep renderer line-heights..." · Reviewed by the reviewer
#9601
2026-10-20 17:41
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough Response summaries and exports now use cursor-based pagination ordered by `createdAt` and `id`. The cursor includes both fields, and the shared filter preserves existing query conditions. Tests cover timestamp ties, deleted cursor rows, and large datasets with and without filters. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to 87467 The pagination change is ready to merge after normal checks; no actionable risk remains identified. Security architecture risk: ⚪ Minimal · up to 87467 The new cursor preserves survey permissions and response filters while correcting batch ordering. The reviewed paths introduc
… the rest (4589 characters)e no material security risk or broader data access. Concurrent reads still do not provide a point-in-time snapshot. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The changed retrieval scope remains the matching responses of the authorized survey. Correcting which rows reach summaries and exports does not expand the inspected readers to other surveys or grant additional authority. **Trust Boundaries and Controls** - _observed_ — The export action authenticates the user and checks survey.response_export before reading response data. Summary actions retain survey.response_read authorization, and the summary page resolves workspace/survey authorization before retrieval. These controls predate the PR and remain on the inspected call paths. **Resilience and Maintainability Implications** - _inferred_ — For stable sort keys, each full batch advances strictly after its final row; empty or short batches terminate, including an empty query after an exact-size final batch. Query failures propagate before a completed summary or export is returned, and interrupted reads leave no durable cursor state. - _inferred_ — The reads provide keyset traversal, not a point-in-time snapshot: concurrent deletions can remove unread rows, and backdated insertions can appear in later batches. Arbitrary changes to existing sort keys remain outside the demonstrated guarantee; this is not an established security regression introduced by the PR. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title uses the required Conventional Commits format, uses a valid lowercase type, has no trailing period, and describes the survey summary and response export pagination fix. | | Description check | | The description is complete and aligned with the template. It explains the defect and fix, links to key code, addresses breaking changes and migrations, and documents test coverage, gaps, and risks. | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | PASS. The PR description has an unticked Breaking changes checkbox. The reviewed diff changes only `apps/web` response pagination, summary/export internals, tests, and a mock. It does not change an SD… | | Only The Ticket Line Carries A Magic Word | | The raw PR description contains one magic-word pair: “Fixes ENG-3753” at the top. The scan found no other `Fixes`, `Closes`, `Resolves`, or `Ref` pair with an `ENG-<number>` or `#<number>` reference. | | Coverage Accounts For The Whole Diff | | The Coverage table is populated and accounts for the changed behavior. It covers summary pagination, export pagination, filter preservation, composite `createdAt`/`id` ordering, tied timestamps, delet… | Full details: Breaking Changes Match The Diff **Explanation** PASS. The PR description has an unticked Breaking changes checkbox. The reviewed diff changes only `apps/web` response pagination, summary/export internals, tests, and a mock. It does not change an SDK surface under `packages/js-core`, `packages/survey-ui`, or `packages/surveys`; routes, environment/config keys, and database migrations are unchanged. The `getResponses` and `getResponsesForSummary` signature changes are internal `apps/web` functions, which this check excludes. The existing response selection shape remains unchanged. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table is populated and accounts for the changed behavior. It covers summary pagination, export pagination, filter preservation, composite `createdAt`/`id` ordering, tied timestamps, deleted cursor rows, and CSV/XLSX results over multiple batches. Each row states a method and outcome. The `Rerun:` commands name the unit suites, and the integration command names `response-pagination.integration.test.ts`. The description includes an explicit Open gaps section with reasons, and it attaches screenshots for the changed summary UI. Comment `@the reviewer help` to get the list of available commands.
#9601
2026-10-20 17:48
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No blocking or non-blocking defects were identified. The change is safe to merge based on the reviewed code and executed checks. Reviews (1) · Last reviewed commit: "fix: page survey summary and response ex..." · Reviewed by the reviewer
#9601
2026-10-20 17:50
cubic
inline · P2
apps/web/lib/response/service.test.ts:642tests
This test can pass when all responses are exported in the wrong order. Assert that `exportedIds` matches the expected `createdAt desc, id desc` sequence. ```suggestion expect(exportedIds).toEqual( [...rows] .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || b.id.localeCompare(a.id)) .map((row) => row.id) ); ```
#9601
2026-10-20 17:50
cubic
review-body
—summary
**1 issue found** across 7 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9614
2026-10-20 17:52
coderabbit
issue-comment
—summary
### Walkthrough The change adds private Depot candidate builds with exported image references, build IDs, and application identities. Docker images now contain an assembled Next.js runtime with metadata and inventory checks. ECR and GHCR release workflows smoke-test candidates on amd64 and arm64 before digest-verified promotion. Pull-request validation also checks runtime integrity and browser hydration. Priority: ➖ Normal Merge Risk: 🔵 Low · up to f0110 Release images are now built as private candidates, checked on both amd64 and arm64, and then promoted by verified digest. Two small follow-ups remain. The build job grants publishing and signing permissions it no longer uses. One test
… the rest (7424 characters)checks exact script text instead of behavior. Neither blocks release correctness, so the change is mergeable with these cleanups. Security architecture risk: 🔵 Low · up to f0110 Release images now pass checks on both supported architectures before release tags move. Existing build-job write permissions remain, but this PR does not introduce them. Partial publication still requires operational forward recovery. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — A compromise of privileged build-side execution can still target existing package-publication or identity-token authority. Release images propagate to community registries and cloud deployment callers, but no new arbitrary destination or unauthenticated entrypoint was established. Actual cloud-role access remains dependent on external trust policies that were not supplied. **Security Findings and Attack Paths** - _observed_ — The retained security finding concerns the build job's package-write and identity-token permissions. Those permissions are identical at the PR base and head. Counterevidence shows that the head's normal build skips destination registry login and saves privately instead of publishing. The finding remains relevant, but comparison did not establish that this PR introduced or worsened its exposure. **Trust Boundaries and Controls** - _inferred_ — The runtime inventory detects mixing or drift of inventoried application output; it is not independent authentication of the compiler or build code because the same build produces both files and hashes. Dependency-package contents and symlinks are deliberately outside this inventory's coverage. - _observed_ — The Depot project credential is used on the validation runner to obtain a masked build-scoped pull token. The shown candidate-container invocation does not forward that credential or mount host credentials; it supplies disposable datastore credentials and placeholder integration secrets instead. **Resilience and Maintainability Implications** - _observed_ — Smoke cleanup is best effort and covers normal exit, errors, INT, and TERM. Startup polling and validation jobs are bounded. Documentation explicitly limits the checks: they do not validate authorization, Kubernetes handoff, mixed-version compatibility, or zero-downtime deployment. **Hardening Proposals** - _proposed_ — Align build-job permissions with private candidate production by removing package-write and identity-token permissions where unused, while retaining destination publication authority only in promotion. - _proposed_ — Confirm saved-build retention and rerun-output guarantees against the forward-recovery procedure, and consider serializing writers to shared release tags so recovery does not race another promotion. Pre-merge checks: 6 · 1 Failed checks: 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | | Only The Ticket Line Carries A Magic Word | | The supplied PR description is truncated after “Depot's multi-pl…”. The visible portion contains no `Fixes`, `Closes`, `Resolves`, or `Ref` pair with `ENG-<number>` or `#<number>`, but the check requi… | Provide the complete raw PR description or enable access to it, then scan every line for the specified magic-word and issue-reference pairs. | Passed checks: 6 passed | Check name | Status | Explanation | | :---------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows the Conventional Commits format and accurately describes the main change: keeping Next.js server and browser assets synchronized. | | Description check | | The description is complete and follows the repository template. It explains the problem and solution, identifies key files, documents breaking changes and migration impact, lists test coverage, and r… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR declares no breaking changes. The diff adds optional workflow/action inputs and Docker build arguments with defaults, and changes internal Docker assembly and CI promotion logic. Existing Docke… | | Coverage Accounts For The Whole Diff | | The description includes six non-placeholder Coverage rows. They cover runtime assembly and verification, diagnostics, build identity and repair-cache behavior, native validation and promotion, startu… | Full details: Description check **Explanation** The description is complete and follows the repository template. It explains the problem and solution, identifies key files, documents breaking changes and migration impact, lists test coverage, and records open gaps and risks. Full details: Breaking Changes Match The Diff **Explanation** The PR declares no breaking changes. The diff adds optional workflow/action inputs and Docker build arguments with defaults, and changes internal Docker assembly and CI promotion logic. Existing Docker builds remain supported without new arguments. The diff does not change an application API shape, route, HTTP status, environment/config default, database schema, or the public SDK surface. Full details: Coverage Accounts For The Whole Diff **Explanation** The description includes six non-placeholder Coverage rows. They cover runtime assembly and verification, diagnostics, build identity and repair-cache behavior, native validation and promotion, startup cleanup, and browser startup. The `Rerun:` command names both unit suites, and each mutation row names the file and line to mutate. Each row states a verification method and an observed outcome. `Open gaps` is present and lists candidate-transfer rehearsal, warm-cache rehearsal, Apollo deployment verification, and known smoke-test limits. The diff changes Docker and CI behavior, but it does not change rendered product UI, so no screenshot is required. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The supplied PR description is truncated after “Depot's multi-pl…”. The visible portion contains no `Fixes`, `Closes`, `Resolves`, or `Ref` pair with `ENG-&lt;number&gt;` or `#&lt;number&gt;`, but the check requires scanning the complete raw description. No full description is available in the repository checkout. **Resolution** Provide the complete raw PR description or enable access to it, then scan every line for the specified magic-word and issue-reference pairs. Comment `@the reviewer help` to get the list of available commands.
#9614
2026-10-20 17:58
coderabbit
inline · P3
docker/__tests__/image-promotion.test.ts:198tests
**Remove the shell-string assertions from this test.** This test checks for exact substrings of the promotion script, such as `'test "$saved" = "${IMAGE##*@}"'`, and for the exact `no-cache-filters` expression. A rename that keeps the same behavior makes it fail. A behavior break that keeps the same text does not. The test at Lines 141-186 already runs the real script and checks the digest binding for source and destination mismatches. Delete the four `publish?.run` substring checks. If the cache-bypass contract needs a guard, assert only the resolved behavior, for example that the `no-cache-filters` value names `installer` and `runner`. As per path instructions: "Assert on inputs and outp
… the rest (129 characters)uts. Flag tests that lock implementation details, markup, or snapshots, and tests that cannot fail." _Source: Path instructions_
#9614
2026-10-20 17:58
coderabbit
review-body
—summary
**Actionable comments posted: 1** > [!CAUTION] > Some comments are outside the diff and can’t be posted inline due to GitHub limitations. > > **⚠️ Outside diff range comments (1)** > > > 🟡 Minor · Remove the build job's unused write permissions. · release-docker-github.yml:42-47 > > `.github/workflows/release-docker-github.yml:42-47` > **🔒 Security & Privacy** | **🛡️ Detected with Advanced Tier** | **🟡 Minor** | **⚡ Quick win** > > > > **Security Misconfiguration** > > **Reachability:** Internal > **Exploitability:** Difficult > **CWE:** CWE-272 > > **Remove the `build` job's unused write permissions.** > > The `build` job passes `registry: ""`, so `docker-build-setup` does not log in t
… the rest (385 characters)o GHCR. Publishing and signing occur in `promote`, which has its own permissions. Keep only `contents: read` on `build`. > > > Proposed fix > > ```diff > permissions: > contents: read > - packages: write > - id-token: write > - # This is used to complete the identity challenge > - # with sigstore/fulcio when running outside of PRs. > ``` > > > > > > > _Source: Path instructions_ > >
#9614
2026-10-20 18:01
greptile
issue-comment
—summary
Confidence Score: 5/5 **[High impact]** No blocking issue was identified in the validated paths; the acknowledged release rehearsal and deployment gaps remain unchanged. Reviews (1) · Last reviewed commit: "fix: keep Next.js server and browser ass..." · Reviewed by the reviewer
#9614
2026-10-20 18:06
cubic
review-body
—summary
**8 issues found** across 21 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9614
2026-10-20 18:06
cubic
inline · P1
.github/workflows/release-docker-github.yml:173defect-concurrency
The smoke checks can leave these precomputed GHCR aliases stale, letting an older release finish validation later and overwrite `:latest` or a major/minor tag. Recheck alias eligibility at promotion time before applying the tags.
#9614
2026-10-20 18:06
cubic
inline · P2
docker/__tests__/image-promotion.test.ts:34tests
These promotion and startup checks never run in CI: the Docker validation job uses an explicit test-file list that omits this file, and the root Vitest config excludes `docker/**`. Add this test path to the Docker validation command so regressions fail CI.
#9614
2026-10-20 18:06
cubic
inline · P2
.github/workflows/release-docker-github-experimental.yml:93defect-concurrency
Concurrent dispatches can promote the mutable branch tag out of order, letting an older validated candidate overwrite the newer experimental image. Serialize promotions and reject stale candidates before updating shared tags.
#9614
2026-10-20 18:06
cubic
inline · P2
apps/web/Dockerfile:391defect-functional
This `RUN` executes during image construction, not when a container starts; `start.sh` proceeds to migrations without verifying the artifact. Invoke the verifier from the startup path before migrations so runtime filesystem changes are caught.
#9614
2026-10-20 18:06
cubic
inline · P2
apps/web/scripts/docker/verify-next-runtime.mjs:46defect-functional
`client-reference-manifest.js` CSS paths (`static/css/...`) are excluded here, so missing App Router stylesheets pass the asset check. Include `css` in the alternatives. ```suggestion for (const [reference] of source.matchAll(/static\/(?:chunks|css|media)\/[a-zA-Z0-9_./-]+/g)) { ```
#9614
2026-10-20 18:06
cubic
inline · P2
docker/smoke-release-image.sh:54defect-functional
This loop can run for about 21 minutes, exceeding the release-validation job's 20-minute timeout before setup and image-pull time. Enforce a shorter total deadline so the script reports the startup failure itself. ```suggestion deadline=$((SECONDS + 300)) for attempt in $(seq 1 180); do if curl --fail --silent --max-time 5 "$origin/health" >/dev/null; then break; fi if [ "$(docker inspect --format '{{.State.Running}}' "$prefix-web")" != true ] || [ "$SECONDS" -ge "$deadline" ]; then echo "Image failed to start" >&2 exit 1 fi sleep 2 done ```
#9614
2026-10-20 18:06
cubic
inline · P3
docker/IMAGE-VALIDATION.md:51docs
Only the smoke commands require matching native architectures; the install and unit-test commands do not. Narrow this instruction to the smoke commands so readers do not repeat the setup and tests on both architectures.
#9614
2026-10-20 18:06
cubic
inline · P3
docker/__tests__/image-promotion.test.ts:191tests
Remove these exact shell-string assertions; the promotion test already executes the script and checks digest-mismatch behavior, so these checks only make harmless shell refactors fail.
#9618
2026-10-20 18:08
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The change adds public show-condition types and Zod schemas with operand validation, tree limits, and empty-group normalization. It adds a typed evaluator for URL, referrer, query-parameter, device, and visit conditions. The evaluator supports text and numeric operators, nested groups, and ordered condition results. Tests cover schema validation and normalization, plus evaluator behavior across valid, invalid, and missing-context cases. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to 9a6a7 This change adds new show-condition validation and evaluation logic that nothing uses yet, so current users and production
… the rest (7060 characters) behavior are unaffected. No blocking issues were found, and the change is ready to merge. Security architecture risk: ⚪ Minimal · up to 9a6a7 The new API changes a shared contract, but it is not connected to application behavior in this PR. Evaluation only computes results from supplied inputs; it introduces no identified security exposure, privilege change, or persistent-state transition. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The effective change is currently confined to a publicly importable library contract. Inspection found no production consumer connecting its outcomes to tenant data, service privileges, persistent storage, or an authorization decision. Future integrations and external consumers remain outside this assessment. **Trust Boundaries and Controls** - _observed_ — The evaluator accepts typed inputs without invoking runtime schema validation, so validation and evaluation are separate boundaries. Missing visit data fails visit conditions; unsupported comparisons return false. The intentional null/empty pass behavior represents unrestricted targeting, not an authorization guarantee. Pre-merge checks: 5 · 1 · 1 Failed checks: 1 warning · 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Coverage Accounts For The Whole Diff | | The Coverage table is populated and its rows include methods, outcomes, test names, and most mutation locations. It does not account for all changed behavior. The diff adds numeric comparisons and dev… | Update `## How this was tested` so every changed behavior is named by a Coverage row or by Open gaps with a reason. Include numeric and device evaluation, group connector and empty-condition semantics, and value trimming, or explicitly list… | | Only The Ticket Line Carries A Magic Word | | The visible description contains the allowed top pair, “Fixes ENG-3925”. It also states “The pull request description was truncated,” and the full raw body is not available in the repository checkout.… | Provide the complete raw pull request description, then scan all lines for `Fixes`, `Closes`, `Resolves`, or `Ref` next to `ENG-<number>` or `#<number>`, excluding only the top ticket line. | Passed checks: 5 passed | Check name | Status | Explanation | | :----------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses the valid feat type and types scope, describes the schema and evaluator change, uses lowercase text, and includes the ENG-3925 reference. | | Description check | | The description includes the required ticket reference, change summary, review locations, breaking-change status, migration notes, test command, coverage table, open gaps, risks, and AI metadata. It c… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description has an unticked breaking-changes checkbox. The reviewed diff adds four new files under `packages/types` only: the schema, evaluator, and tests. It does not change request/response s… | Full details: Description check **Explanation** The description includes the required ticket reference, change summary, review locations, breaking-change status, migration notes, test command, coverage table, open gaps, risks, and AI metadata. It clearly explains the new schema and evaluator. Full details: Breaking Changes Match The Diff **Explanation** The PR description has an unticked breaking-changes checkbox. The reviewed diff adds four new files under `packages/types` only: the schema, evaluator, and tests. It does not change request/response shapes, routes, environment or config keys, database schema, or exported SDK signatures in `packages/js-core`, `packages/survey-ui`, or `packages/surveys`. New workspace-package types and functions are explicitly outside this check. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table is populated and its rows include methods, outcomes, test names, and most mutation locations. It does not account for all changed behavior. The diff adds numeric comparisons and device operand evaluation (`packages/types/show-conditions-evaluate.ts:93-98,149-164`), AND/OR nested-group and empty-condition evaluation (`:187-201,216`), and schema value trimming (`packages/types/show-conditions.ts:225-229`), but no Coverage row or Open gaps entry names these behaviors. The Open gaps entry only covers future SDK/editor/API wiring. In addition, the mutation references `:140` and `:66` omit the file name, so they are not complete `file:line` locations. **Resolution** Update `## How this was tested` so every changed behavior is named by a Coverage row or by Open gaps with a reason. Include numeric and device evaluation, group connector and empty-condition semantics, and value trimming, or explicitly list any unverified item as a gap. Complete the mutation anchors as `packages/types/show-conditions.ts:140` and `packages/types/show-conditions.ts:66`. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The visible description contains the allowed top pair, “Fixes ENG-3925”. It also states “The pull request description was truncated,” and the full raw body is not available in the repository checkout. Therefore, I cannot verify that no additional magic-word pair appears later. **Resolution** Provide the complete raw pull request description, then scan all lines for `Fixes`, `Closes`, `Resolves`, or `Ref` next to `ENG-&lt;number&gt;` or `#&lt;number&gt;`, excluding only the top ticket line. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 4 - Waived: $1.00 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9618
2026-10-20 18:14
cubic
review-body
—summary
**No issues found** across 4 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9618
2026-10-20 18:16
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Low impact]** The changes add unused show\-rule tools in the shared types package\. Safe to merge with two non-blocking correctness concerns in the currently unused evaluator. Findings 1. **Unsupported conditions can pass** 2. **Empty children change results** Reviews (1) · Last reviewed commit: "feat(types): add the shared show-conditi..." · Reviewed by the reviewer
#9618
2026-10-20 18:16
greptile
inline · P2
packages/types/show-conditions-evaluate.ts:177defect-functional
**Unsupported conditions can pass** Direct callers can receive a passing result for a comparison that the shared schema rejects. For example, `currentUrl` with `isSet` fits `TShowConditionSingle` and evaluates to true, although that operator is allowed only for query parameters. Dispatching by text or number does not enforce the operand-specific operator restrictions, contrary to the evaluator’s documented fail-closed behavior. This is non-blocking because nothing imports the new evaluator yet, and schema validation rejects these combinations. Before callers rely on direct evaluation, check operand/operator compatibility without importing Zod and add regression coverage. Artifacts **Comp
… the rest (1096 characters)lete focused test source for unsupported conditions** - The full authored nine-case test imports the actual schema and evaluator and supports contract and characterization runs, making the reproduction independently repeatable. **Fail-closed contract run with unsupported combinations** - Executed all nine cases expecting unsupported combinations to fail; five assertions failed because schema-rejected conditions evaluated true, reproducing the defect. **Characterization run confirming actual evaluator behavior** - Re-executed the same nine inputs against unchanged production code with assertions for the observed outcomes; all nine passed, confirming the schema/evaluator mismatch. **Strict TypeScript check of the reproduction** - Compiled the complete test with strict TypeScript checking and no emit; exit code zero confirms that the unsupported combinations are type-compatible. **Production source unchanged after validation** - Checked the schema and evaluator against HEAD and listed the saved proof files; no production diff was present. Ran code and verified through TREX
#9618
2026-10-20 18:16
greptile
inline · P2
packages/types/show-conditions-evaluate.ts:193defect-functional
**Empty children change results** An `or` group containing a failing condition and an empty child passes when evaluated directly: the empty child returns true and satisfies the parent. Parsing the same schema-accepted tree removes that child, after which evaluation fails. Hand-built conditions therefore produce different results before and after normalization. This is a non-blocking consistency issue in the currently unused evaluator. Ignore empty descendants when combining results, while keeping a wholly empty tree passing, and add a regression comparing raw and parsed evaluation. Artifacts **Focused TypeScript reproduction source** - The authored script imports the actual schema and e
… the rest (595 characters)valuator and asserts five fixtures in raw and parsed modes, making the confirmed discrepancy reproducible. **Evaluation before schema normalization** - Executed the script in raw mode and captured its command, working directory, exit code, inputs, and results; the mixed OR tree passes despite its failing leaf. **Evaluation after schema normalization** - Executed the same script in parsed mode and captured its command, working directory, exit code, inputs, and results; pruning the empty child makes the mixed OR tree fail while empty trees still pass. Ran code and verified through TREX
#9602
2026-10-20 18:18
cubic
review-body
—summary
**No issues found** across 1 file View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9602
2026-10-20 18:18
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The one-click setup guide now explains that every A and AAAA record must point to the server and describes port 80/443 access for IPv4 and IPv6. Troubleshooting covers separate IPv4 and IPv6 firewall rules and explains how an incorrect AAAA record can affect Let’s Encrypt validation. The guide also removes a malformed link fragment. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to 25f5e This documentation-only change introduces no runtime or deployment behavior changes; the remaining IPv6 issuance uncertainty does not establish a merge-blocking risk. Pre-merge checks: 7 | Check name | Status | Explanation | |
… the rest (2993 characters) :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses an allowed type and scope, describes the documentation change, uses a lowercase summary without a period, and includes the ENG-3467 reference. | | Description check | | The description is substantially complete and matches the template. It includes the ticket reference, What & why, review locations, breaking-change status, migrations, testing coverage, open gaps, and… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description has an unticked breaking-changes checkbox. The reviewed diff modifies only `docs/self-hosting/setup/one-click.mdx`; it changes documentation text and removes a malformed link. It do… | | Only The Ticket Line Carries A Magic Word | | The only magic-word pair is the ticket line `Fixes ENG-3467` at the top of the description. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-<number>` or `#<number>` reference. | | Coverage Accounts For The Whole Diff | | PASS. The diff changes only `docs/self-hosting/setup/one-click.mdx`, so it introduces no product behavior. The description explicitly states “None. Docs-only.” The “Coverage” table is non-empty and gi… | Full details: Description check **Explanation** The description is substantially complete and matches the template. It includes the ticket reference, What & why, review locations, breaking-change status, migrations, testing coverage, open gaps, and risks. The documented changes match the PR objectives. Full details: Breaking Changes Match The Diff **Explanation** The PR description has an unticked breaking-changes checkbox. The reviewed diff modifies only `docs/self-hosting/setup/one-click.mdx`; it changes documentation text and removes a malformed link. It does not rename or retype an API field, change a route or status, alter configuration defaults, add a migration, or change the listed SDK surfaces. Full details: Coverage Accounts For The Whole Diff **Explanation** PASS. The diff changes only `docs/self-hosting/setup/one-click.mdx`, so it introduces no product behavior. The description explicitly states “None. Docs-only.” The “Coverage” table is non-empty and gives a verification method and observed outcome for page rendering, AAAA-only access, the IPv4 fallback claim, and port roles. “Open gaps” is present and records the lack of a real IPv6 issuance test. Comment `@the reviewer help` to get the list of available commands.
#9602
2026-10-20 18:27
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Low impact]** Safe to merge. No actionable defects were found in the changed documentation. Reviews (1) · Last reviewed commit: "docs(self-hosting): document AAAA record..." · Reviewed by the reviewer
#9598
2026-10-20 18:29
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The Sonar configuration adds exclusions for `typescript:S9382` across all files, `typescript:S6564` in `packages/jobs/src/types.ts`, and `javascript:S9382` across all files. It documents these exclusions and expands the multicriteria list from `e1` to `e1,e2,e3,e4`. The existing `typescript:S1313` exclusion remains unchanged. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to 411e7 The next Sonar analysis will omit S9382 findings across TypeScript and JavaScript and S6564 findings in the job-types file. The checked settings match the stated intent, with no concrete merge-blocking risk established. Architecture r
… the rest (4282 characters)isk: 🔵 Low · up to 411e7 The change affects 1 system. **Changed systems:** `sonar-project.properties` **Architecture concerns** No architecture-level concerns identified. Review details **Systems and components** - _observed_ — sonar-project.properties (service) was modified; 1 changed file maps to changed impact. **Before / after behavior** - _observed_ — Modified behavior in sonar-project.properties: Added documentation for the new Sonar exclusions covering sequential promise awaits in TypeScript and JavaScript, plus the intentional type alias in `packages/jobs/src/types.ts`. - _observed_ — Modified behavior in sonar-project.properties: Expanded `sonar.issue.ignore.multicriteria` from only `e1` to `e1,e2,e3,e4`, adding exclusions for `typescript:S9382` globally, `typescript:S6564` in `packages/jobs/src/types.ts`, and `javascript:S9382` globally. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses a valid type and scope, has a lowercase descriptive summary without a trailing period, and includes the ENG-3871 reference. | | Description check | | The description includes the required ticket reference, change summary, review location, breaking-change status, migration details, testing coverage, open gaps, and risks. It clearly describes the sta… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The checkbox is unticked, and the diff changes only `sonar-project.properties` issue-exclusion settings and comments. It does not change an API shape, endpoint, webhook payload, HTTP status, environme… | | Only The Ticket Line Carries A Magic Word | | The only magic-word and ticket-id pair in the PR description is the top ticket line, `Fixes ENG-3871`. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-<number>` or `#<number>`… | | Coverage Accounts For The Whole Diff | | PASS. The diff changes only `sonar-project.properties`, so it changes static-analysis configuration and no product behavior or rendered UI. The description includes a non-empty Coverage table with ver… | Full details: Description check **Explanation** The description includes the required ticket reference, change summary, review location, breaking-change status, migration details, testing coverage, open gaps, and risks. It clearly describes the static-analysis configuration change and its validation limits. Full details: Breaking Changes Match The Diff **Explanation** The checkbox is unticked, and the diff changes only `sonar-project.properties` issue-exclusion settings and comments. It does not change an API shape, endpoint, webhook payload, HTTP status, environment variable, required config key, database schema, or the listed public SDK surfaces. The change is static-analysis configuration only. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The only magic-word and ticket-id pair in the PR description is the top ticket line, `Fixes ENG-3871`. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Coverage Accounts For The Whole Diff **Explanation** PASS. The diff changes only `sonar-project.properties`, so it changes static-analysis configuration and no product behavior or rendered UI. The description includes a non-empty Coverage table with verification methods and observed outcomes, including the multicriteria syntax, both S9382 language rules, and the retained review checks. The Open gaps section explicitly lists post-merge verification for S9382 and S6564 and explains that the effect appears on the next `main` analysis. Comment `@the reviewer help` to get the list of available commands.
#9598
2026-10-20 18:29
cubic
review-body
—summary
**No issues found** across 1 file View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9598
2026-10-20 18:33
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge: the change is limited to static-analysis configuration and does not change application behavior. Reviews (1) · Last reviewed commit: "chore(sonar): stop two rules from flaggi..." · Reviewed by the reviewer
#9515
2026-10-20 18:40
coderabbit
issue-comment
—summary
### Walkthrough Feedback-record queries now support inclusive `updated_since` and `updated_until` filters, plus ascending `updated_at` sorting. API and MCP documentation describes incremental synchronization, including overlap windows and deletion reconciliation. Hub image defaults change to version 0.14.0, and compatibility comments now refer to Hub versions older than hub#125. Priority: ➖ Normal Merge Risk: 🔵 Low · up to 58351 Add upgrade guidance for the changed Hub image default. No separate feedback-record ordering failure is established by the available evidence. Security architecture risk: 🔵 Low · up to 58351 The added read options preserve existing dataset authorization. Rema
… the rest (6493 characters)ining uncertainty concerns the upgraded data service’s synchronization semantics and migration recovery, rather than a demonstrated expansion of access. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The added filter path remains bounded locally to the caller’s authorized dataset. The deployment change has broader scope: it replaces the shared Hub API, worker, and migration image operating against the configured Hub database. Local authorization evidence does not establish the upgraded service’s internal isolation or migration safety. **Trust Boundaries and Controls** - _observed_ — Caller-controlled MCP filters pass through strict schemas and an explicit mapper, while tenant_id is injected from authorized dataset resolution. REST preserves its existing gateway authorization before the proxy uses the service credential. No new tenant-selection or credential authority was established by the added parameters. **Resilience and Maintainability Implications** - _inferred_ — Deletion propagation to downstream copies depends on clients completing periodic reconciliation; it is not provided by updated_since. The new documentation makes this limitation explicit, including deletion of a user’s records. This is a client-owned retention boundary, not evidence that this PR introduces a deletion bypass. Pre-merge checks: 5 · 1 · 1 Failed checks: 1 warning · 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Breaking Changes Match The Diff | | The PR description leaves `- [ ] This PR contains breaking changes` unticked, but the diff changes deployment defaults. `charts/formbricks/values.yaml:1178` changes the default Hub digest and `:1180` … | Tick `- [x] This PR contains breaking changes` and document the Hub image default changes, or restore the previous defaults. | | Only The Ticket Line Carries A Magic Word | | The supplied pull request description is explicitly truncated after the `Risks` section. The visible body contains the allowed top line `Fixes ENG-3420` and no other visible magic-word pair, but the o… | Provide the complete raw pull request description, including the text after the truncation, and scan it for `Fixes`, `Closes`, `Resolves`, or `Ref` next to `ENG-<number>` or `#<number>`. | Passed checks: 5 passed | Check name | Status | Explanation | | :---------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows the Conventional Commits format, uses a valid type and scope, describes the incremental extraction change, uses lowercase text without a period, and includes the ENG-3420 reference. | | Description check | | The description includes the required ticket reference, change summary, locations, breaking-change declaration, migration notes, test coverage, open gaps, and risks. It is complete and directly relate… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Coverage Accounts For The Whole Diff | | The Coverage table is populated and covers the changed behavior. It names unit tests for range and `sort=updated_at` forwarding, a unit guard for MCP filter exposure, manual REST/MCP validation on Hub… | Full details: Description check **Explanation** The description includes the required ticket reference, change summary, locations, breaking-change declaration, migration notes, test coverage, open gaps, and risks. It is complete and directly related to the changes. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table is populated and covers the changed behavior. It names unit tests for range and `sort=updated_at` forwarding, a unit guard for MCP filter exposure, manual REST/MCP validation on Hub `0.14.0`, and API specification checks. The mutation row identifies `operations.ts:176-177` and gives the observed failure condition. The Open gaps subsection is present and records the gateway limitation. The diff changes no rendered UI. Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves `- [ ] This PR contains breaking changes` unticked, but the diff changes deployment defaults. `charts/formbricks/values.yaml:1178` changes the default Hub digest and `:1180` changes `hub.image.tag` from `0.8.5` to `0.14.0`. `docker-compose.dev.yml:221`, `:236`, and `:300` change the fallback `HUB_IMAGE_TAG` from `0.8.5` to `0.14.0`. The check explicitly treats changed defaults, including chart values, as breaking. **Resolution** Tick `- [x] This PR contains breaking changes` and document the Hub image default changes, or restore the previous defaults. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The supplied pull request description is explicitly truncated after the `Risks` section. The visible body contains the allowed top line `Fixes ENG-3420` and no other visible magic-word pair, but the omitted text cannot be scanned. The repository contains no full PR-description artifact; the commit message is not the description. **Resolution** Provide the complete raw pull request description, including the text after the truncation, and scan it for `Fixes`, `Closes`, `Resolves`, or `Ref` next to `ENG-&lt;number&gt;` or `#&lt;number&gt;`. > [!WARNING] > ⚠️ This pull request shows signs of AI-generated slop (description_diff_mismatch). It has been flagged by the reviewer slop detection and should be reviewed carefully. Comment `@the reviewer help` to get the list of available commands.
#9515
2026-10-20 18:45
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No blocking issues were identified in the validated changes; deployment remains subject to the acknowledged Hub upgrade dependency. Reviews (1) · Last reviewed commit: "feat(feedback-records): expose updated\_s..." · Reviewed by the reviewer
#9515
2026-10-20 18:45
coderabbit
inline · P2
charts/formbricks/values.yaml:1180docs
**Document the Hub image default change as a breaking change.** `hub.image.digest` and the fallback `hub.image.tag` change from Hub `0.8.5` to `0.14.0`. The README's existing breaking-change entry covers migration Job security context, not this image update. Add a Hub image upgrade entry that states whether existing installs need manual action and describes that action when required. _Source: Path instructions_
#9515
2026-10-20 18:45
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9515
2026-10-20 18:48
cubic
inline · P2
docs/api-v3-reference/introduction.mdx:137docs
An HTTP `Date` header uses HTTP-date syntax, while these filters are documented as ISO 8601 timestamps. Tell clients to parse the header and convert it to ISO 8601 UTC before sending either filter. ```suggestion is this run's `updated_until`, and _S_ minus a few minutes is the next run's `updated_since`. Parse the HTTP-date as an ISO 8601 UTC timestamp before sending either filter, and take both from the same _S_ — an `updated_until` from your own clock can fall short of the next run's `updated_since` and leave records that neither run ever reads. ```
#9515
2026-10-20 18:48
cubic
review-body
—summary
**4 issues found** across 12 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9515
2026-10-20 18:48
cubic
inline · P2
docs/api-v3-reference/introduction.mdx:130docs
The `updated_until` bound excludes updates stamped after S, so changes made while the traversal runs will normally appear in the next overlapping run, not later in this one. Limit the same-run guarantee to updates stamped at or before S. ```suggestion records it returns. Changes stamped after this run's `updated_until` are picked up by the next overlapping run; changes stamped at or before the bound can move ahead of the cursor and reappear in this run, so upsert by `id`. `updated_at` can only be sorted ascending: descending, or with no ```
#9515
2026-10-20 18:48
cubic
inline · P2
docs/development/technical-handbook/mcp-server.mdx:1502docs
MCP callers cannot follow the referenced watermark procedure: it relies on the REST response's `Date` header, which the MCP tool result drops. Clarify that reliable repeated syncs must use REST, or expose a usable watermark through MCP. ```suggestion v3 API introduction; its safe watermark procedure requires the REST response's `Date` header, which MCP tool results do not expose, so use REST for repeated syncs. ```
#9515
2026-10-20 18:48
cubic
inline · P2
charts/formbricks/values.yaml:1178docs
Add a Hub image upgrade note explaining whether existing installs with an older image override must update it and what to change. Otherwise, those installs can keep running a Hub version that ignores the new filters and rejects `sort=updated_at`.
#9555
2026-10-20 18:51
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough OAuth consent approval and updates now reconcile existing token grants against the effective consent scopes. Refresh validation rejects grants that exceed that scope bound and returns `invalid_grant`. The after-auth hook runs consent-narrowing cleanup. The OAuth test helper can request the consent screen, and integration tests cover narrowing, revocation, legacy grants, isolation, and refresh races. Setup documentation describes scope reduction and token lifetimes. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to 8e55a Narrowing an app's OAuth consent now ends existing tokens that exceed the new scopes, and r
… the rest (7445 characters)efresh attempts beyond the current consent are rejected with `invalid_grant`. Grants still within scope keep working. No concrete defect was identified, and the change appears ready to merge after normal checks. Security architecture risk: 🔵 Low · up to 8e55a The change strengthens consent enforcement. No new cross-account exposure was established, but immediate revocation has documented limits and not all authorization behavior could be independently verified. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _observed_ — Cleanup can affect multiple grants and sessions, but its consent and token predicates are bounded to one user and OAuth client. Inspected integration assertions preserve other users' tokens and the same user's other clients. **Security Findings and Attack Paths** - _observed_ — The addressed attack path is continued refresh using an older, wider credential after the user narrows consent. The new check examines stored token scopes, not merely scopes requested for the current refresh, and also rejects a covered child when its identifiable grant still exceeds consent. **Trust Boundaries and Controls** - _observed_ — Consent update and deletion resolve consent ownership using the authenticated user's identity. Approval client identity is read from provider request state rather than a body-supplied client ID. Refresh enforcement derives user and client identity from the stored token and preserves the existing skipConsent-client exemption. **Resilience and Maintainability Implications** - _observed_ — Refresh validation runs after issuance and reads consent under a shared lock, allowing it to wait for in-flight narrowing before releasing credentials. Cleanup deletes affected refresh tokens instead of marking them revoked, avoiding the provider reuse-detection behavior that could otherwise remove a newly narrowed grant. Integration source checks the narrowing race and legacy refused-refresh cleanup. Pre-merge checks: 4 · 1 · 2 Failed checks: 1 warning · 2 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Breaking Changes Match The Diff | | The Breaking changes checkbox is unticked, but the diff changes an existing `/oauth2/token` refresh response. In `apps/web/modules/auth/lib/oauth-grant-revocation.ts:533-553`, a refresh token whose sc… | Tick `- [x] This PR contains breaking changes` in the PR description and document the `/oauth2/token` refresh response change for tokens beyond narrowed consent. | | Only The Ticket Line Carries A Magic Word | | The visible description contains the allowed top-line pair `Fixes ENG-3529`. It also contains `ENG-2499` without a magic word. However, the supplied description is explicitly truncated, so the complet… | Provide the complete pull request description, including the omitted text after `B. narrowed by`, and rescan every occurrence of `Fixes`, `Closes`, `Resolves`, or `Ref` next to `ENG-<number>` or `#<number>`. | | Coverage Accounts For The Whole Diff | | The authoritative diff changes OAuth consent narrowing, refresh validation, token cleanup, hook execution, and test helpers across seven files. The visible Coverage table names the main narrowing, ref… | Provide the complete PR description, including the end of the folded testing section and the Open gaps subsection, so every changed behavior and required QA marker can be checked. | Passed checks: 4 passed | Check name | Status | Explanation | | :------------------------ | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows the Conventional Commits format, describes the OAuth consent narrowing fix, uses an allowed type, and includes the ENG-3529 reference without a trailing period. | | Description check | | The description includes the required ticket reference, Was/Now summary, implementation links, breaking-change status, migration notes, test command, coverage, gaps, and risks. It clearly explains the… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | Full details: Description check **Explanation** The description includes the required ticket reference, Was/Now summary, implementation links, breaking-change status, migration notes, test command, coverage, gaps, and risks. It clearly explains the user-visible OAuth token behavior. Full details: Breaking Changes Match The Diff **Explanation** The Breaking changes checkbox is unticked, but the diff changes an existing `/oauth2/token` refresh response. In `apps/web/modules/auth/lib/oauth-grant-revocation.ts:533-553`, a refresh token whose scopes exceed narrowed consent is now classified as `consent_narrowed` and throws `invalidGrant()` (`BAD_REQUEST`, HTTP 400). That case previously succeeded with HTTP 200. This matches the check's status-code breaking-change condition. **Resolution** Tick `- [x] This PR contains breaking changes` in the PR description and document the `/oauth2/token` refresh response change for tokens beyond narrowed consent. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The visible description contains the allowed top-line pair `Fixes ENG-3529`. It also contains `ENG-2499` without a magic word. However, the supplied description is explicitly truncated, so the complete body cannot be scanned for additional magic-word pairs. **Resolution** Provide the complete pull request description, including the omitted text after `B. narrowed by`, and rescan every occurrence of `Fixes`, `Closes`, `Resolves`, or `Ref` next to `ENG-&lt;number&gt;` or `#&lt;number&gt;`. Full details: Coverage Accounts For The Whole Diff **Explanation** The authoritative diff changes OAuth consent narrowing, refresh validation, token cleanup, hook execution, and test helpers across seven files. The visible Coverage table names the main narrowing, refresh, race, isolation, and manual checks. However, the supplied PR description is truncated inside the folded live-run section at “B. narrowed by”. The complete body is not present in the repository, and native issue lookup is unavailable. Therefore, I cannot verify the required Open gaps subsection or any omitted Coverage content. **Resolution** Provide the complete PR description, including the end of the folded testing section and the Open gaps subsection, so every changed behavior and required QA marker can be checked. Comment `@the reviewer help` to get the list of available commands.
#9555
2026-10-20 18:59
cubic
inline · P2
apps/web/modules/auth/lib/oauth-grant-revocation.ts:536defect-security
Pre-1.7 refresh chains have no `authorizationCodeId`, so this skips the whole-grant check for them. A narrowed child whose own scopes fit the consent can therefore keep refreshing while its stored ancestor exceeds consent; handle legacy-chain ancestry too.
#9555
2026-10-20 18:59
cubic
review-body
—summary
**1 issue found** across 7 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9555
2026-10-20 19:00
greptile
issue-comment
—summary
Confidence Score: 5/5 **[High impact]** No blocking findings remain; the reviewed changes are safe to merge within the documented OAuth limitations. Reviews (1) · Last reviewed commit: "fix(auth): end an OAuth app's tokens tha..." · Reviewed by the reviewer
#9617
2026-10-20 19:02
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The change adds a Rollup output option that prevents UMD bundles from selecting AMD loading, applies it to the js-core and survey builds, and tests browser-global and CommonJS output. The Playwright page template keeps an AMD loader stub available. Generated and documented HTML snippets now call setup from external script onload handlers instead of fixed delays. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to d5fa9 This change lets the SDK and survey renderer load on pages with an AMD loader, and runs setup when the script loads instead of after a fixed delay. No merge-blocking risk was found. RequireJS cons
… the rest (6962 characters)umers that load the bundle through require() will no longer get a module value and should use the window globals, which the PR already notes. Security architecture risk: 🔵 Low · up to d5fa9 The change preserves the inspected configuration and privilege boundaries while fixing loading on AMD-enabled pages. It intentionally changes AMD module compatibility, and delivery of the fix requires refreshing both cached browser bundles. No introduced security concern was established; production cache and rollback behavior remain unverified. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The effective changed scope is host pages loading the upgraded served UMD bundles, including AMD-enabled pages where the factories previously did not run. Execution remains in the embedding page's existing JavaScript context, using the same configured SDK and renderer sources; no additional tenant or server privilege was identified in this loading change. **Trust Boundaries and Controls** - _inferred_ — The changed callback supplies no new attacker-controlled configuration field or script URL. A host page already able to execute JavaScript can influence globals and loader symbols; the renamed AMD lookup does not grant a less-privileged actor a new independently attackable capability. **Resilience and Maintainability Implications** - _inferred_ — An old cached AMD-incompatible SDK can still leave window.formbricks unavailable. This is not a newly introduced failure boundary: the baseline timer callback also dereferenced that global without a guard. The new sequencing removes the arbitrary timer but does not itself guarantee successful bundle delivery or setup recovery. Pre-merge checks: 5 · 2 Failed checks: 2 warnings | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Description check | | The description includes the required sections, implementation details, migration guidance, test coverage, open gaps, and risks. However, it documents a changed RequireJS module-loading behavior while… | Tick the breaking-changes checkbox and replace "None" with the required table describing the RequireJS behavior change, affected consumers, and required action. If this behavior is not externally supported, remove the claim that RequireJS c… | | Coverage Accounts For The Whole Diff | | The Coverage table accounts for the changed AMD bundle behavior, CommonJS behavior, e2e journey, and script-load setup. However, the diff changes rendered UI text: `htmlSnippet` and the HTML portion o… | Attach a screenshot or screencast of the affected App Connection installation snippets, or document the required visual verification with the media in the PR description. | Passed checks: 5 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows the Conventional Commits format, uses a lowercase summary, includes the valid ENG-3657 reference, and accurately describes the AMD-loader fix. | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description has an unticked breaking-change checkbox. The reviewed diff adds UMD build configuration and tests, updates script initialization, and adds an AMD e2e stub. It does not change an AP… | | Only The Ticket Line Carries A Magic Word | | The only magic-word pair in the PR description is the top ticket line, “Fixes ENG-3657”. No other occurrence of “Fixes”, “Closes”, “Resolves”, or “Ref” appears next to an ENG ticket ID or a GitHub iss… | Full details: Breaking Changes Match The Diff **Explanation** The PR description has an unticked breaking-change checkbox. The reviewed diff adds UMD build configuration and tests, updates script initialization, and adds an AMD e2e stub. It does not change an API request/response shape, route, webhook, status code, environment/config key, database schema, or a public SDK signature. The RequireJS loading behavior noted in the PR is outside the listed breaking-change conditions. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The only magic-word pair in the PR description is the top ticket line, “Fixes ENG-3657”. No other occurrence of “Fixes”, “Closes”, “Resolves”, or “Ref” appears next to an ENG ticket ID or a GitHub issue reference. Full details: Description check **Explanation** The description includes the required sections, implementation details, migration guidance, test coverage, open gaps, and risks. However, it documents a changed RequireJS module-loading behavior while leaving the breaking-changes checkbox unticked. **Resolution** Tick the breaking-changes checkbox and replace "None" with the required table describing the RequireJS behavior change, affected consumers, and required action. If this behavior is not externally supported, remove the claim that RequireJS consumers are affected and explain why. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table accounts for the changed AMD bundle behavior, CommonJS behavior, e2e journey, and script-load setup. However, the diff changes rendered UI text: `htmlSnippet` and the HTML portion of `aiPrompt` are passed to `CodeBlock` in `InstallMethodCards`. The description has no screenshot or screencast. **Resolution** Attach a screenshot or screencast of the affected App Connection installation snippets, or document the required visual verification with the media in the PR description. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 7 - Waived: $1.75 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9617
2026-10-20 19:02
cubic
review-body
—summary
**No issues found** across 7 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9617
2026-10-20 19:12
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The change affects browser SDK and survey loading in AMD\-enabled host pages\. Not safe to merge until the snippets wait for DOM readiness before starting setup, because affected returning visitors cannot complete SDK initialization. Findings 1. **Early loading stops setup** Reviews (1) · Last reviewed commit: "fix(js-core): load the SDK and survey re..." · Reviewed by the reviewer
#9617
2026-10-20 19:12
greptile
inline · P1
apps/web/modules/workspaces/settings/(setup)/app-connection/page.tsx:44defect-functional
**Early loading stops setup** If the SDK loads while a later script blocks parsing of the head, this `onload` callback calls `setup()` before the body exists. For returning visitors with valid saved settings, setup skips the state requests and reaches `document.body.appendChild(liveRegion)`, which throws before initialization is marked complete. The SDK catches the exception, but subsequent actions are rejected because setup never finished, preventing surveys from initializing on that visit. Wait for DOM readiness as well as script loading before calling `setup()` in both generated snippets and `docs/formbricks.js`. This initialization regression must be fixed before merging. Artifacts *
… the rest (1446 characters)*Executed Chromium reproduction source** - Runs the actual snippets and built SDK through nine controlled HTTP cases with timing, exception, request, and setup-outcome assertions, making the confirmation reproducible. **Production SDK build output** - Captures the SDK build command, working directory, exit code, and successful Vite output, establishing that the browser used an actual production build. **Prior timer completes setup** - Records Chromium executing the parent snippet with the delayed head script and saved configuration, showing successful setup and an accepted action. **Prior timer setup results** - Captures the browser-rendered timing and outcome values from the prior-timer run, showing a present live region and completed setup. **New onload callback fails before the body exists** - Records Chromium executing the changed snippet under the same conditions, showing the caught appendChild error and incomplete setup despite a resolved promise. **New onload callback failure results** - Captures the browser-rendered error and DOM timing from the changed-snippet run, showing that setup failed before the body was parsed. **Nine-case Chromium execution output** - Captures the executed command and exit code, exact source excerpts, SDK hash, browser version, runtime stacks, timing, requests, and passing assertions, confirming the candidate across all three snippet copies. Ran code and verified through TREX
#9607
2026-10-20 19:14
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough Switcher dropdowns now use shared content styling and scrollable item groups. The settings sidebar delegates switcher rendering to the shared component. API key workspace and permission selection now use comboboxes with translated permission labels. The Playwright utility selects the Manage permission through the updated control. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to c9439 This change makes switcher menus scroll only in the list, and replaces API key workspace and permission pickers with searchable comboboxes. No merge-blocking issue was identified. Cross-browser and short-viewport behavior could use
… the rest (6571 characters) a quick manual check. Pre-merge checks: 5 · 2 Failed checks: 2 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Only The Ticket Line Carries A Magic Word | | The supplied PR description is explicitly truncated during the screenshot section. The visible text contains no `Fixes`, `Closes`, `Resolves`, or `Ref` pair beside an issue reference, but the omitted … | Provide the complete raw PR description, including the omitted screenshot section, then scan every line for `Fixes`, `Closes`, `Resolves`, or `Ref` next to `ENG-<number>` or `#<number>`. Exclude only a valid pair on the top ticket line. | | Coverage Accounts For The Whole Diff | | The diff changes several rendered switchers, shared scroll behavior, API-key workspace and permission pickers, translations, and the related e2e helper. The visible Coverage table provides rows for th… | Provide the complete PR description, including the Open gaps subsection after the truncated screenshot content. Confirm that it lists the untested landing-sidebar switcher and any other changed behavior without a Coverage row, with a reason… | Passed checks: 5 passed | Check name | Status | Explanation | | :----------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits, uses the allowed `fix` type and `web` scope, has a lower-case descriptive summary, and matches the switcher and picker changes. | | Description check | | The description is complete and aligned with the template. It explains the user-visible changes, lists review locations, records breaking-change and migration status, documents test coverage, and iden… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description has an unticked Breaking changes checkbox. The reviewed diff contains only web UI, localization, styling, and Playwright changes. The API-key modal still submits the same `label`, `… | Full details: Description check **Explanation** The description is complete and aligned with the template. It explains the user-visible changes, lists review locations, records breaking-change and migration status, documents test coverage, and identifies open gaps and risks. Full details: Breaking Changes Match The Diff **Explanation** The PR description has an unticked Breaking changes checkbox. The reviewed diff contains only web UI, localization, styling, and Playwright changes. The API-key modal still submits the same `label`, `workspacePermissions`, and `organizationAccess` fields, with the existing `ApiKeyPermission` values preserved at `apps/web/modules/organization/settings/api-keys/components/add-api-key-modal.tsx:160-170`. No endpoint, route, environment/config key, database schema, HTTP status, or SDK surface change appears in the diff. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The supplied PR description is explicitly truncated during the screenshot section. The visible text contains no `Fixes`, `Closes`, `Resolves`, or `Ref` pair beside an issue reference, but the omitted text cannot be scanned. The repository refs contain the commit message and code only, not the complete PR description. **Resolution** Provide the complete raw PR description, including the omitted screenshot section, then scan every line for `Fixes`, `Closes`, `Resolves`, or `Ref` next to `ENG-&lt;number&gt;` or `#&lt;number&gt;`. Exclude only a valid pair on the top ticket line. Full details: Coverage Accounts For The Whole Diff **Explanation** The diff changes several rendered switchers, shared scroll behavior, API-key workspace and permission pickers, translations, and the related e2e helper. The visible Coverage table provides rows for the main navigation, breadcrumbs, settings sidebar, API-key pickers, scrollbar styling, and API-key creation. However, the supplied PR description is truncated inside the screenshot fold, so the required Open gaps subsection and any later coverage details cannot be verified. The objectives mention an untested landing-sidebar switcher, but they do not prove that the actual description lists it under Open gaps. **Resolution** Provide the complete PR description, including the Open gaps subsection after the truncated screenshot content. Confirm that it lists the untested landing-sidebar switcher and any other changed behavior without a Coverage row, with a reason. > [!WARNING] > Some tools did not complete. Review the errors below. > > > 🔧 Biome (2.5.14) > > > > > apps/web/modules/ui/globals.css > > > > File contains syntax errors that prevent linting: Line 3: Tailwind-specific syntax is disabled.; Line 4: Tailwind-specific syntax is disabled.; Line 5: Tailwind-specific syntax is disabled.; Line 8: Tailwind-specific syntax is disabled.; Line 11: Tailwind-specific syntax is disabled.; Line 13: Tailwind-specific syntax is disabled.; Line 273: Tailwind-specific syntax is disabled.; Line 276: Tailwind-specific syntax is disabled.; Line 281: Tailwind-specific syntax is disabled.; Line 287: Tailwind-specific syntax is disabled.; Line 318: Tailwind-specific syntax is disabled. > > > > > > > > ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 11 - Waived: $2.75 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9607
2026-10-20 19:21
cubic
review-body
—summary
**No issues found** across 26 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9607
2026-10-20 19:27
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No blocking issues were identified; the exercised switcher and API-key dialog interactions support merging this UI change. Reviews (1) · Last reviewed commit: "fix(web): unify workspace pickers and st..." · Reviewed by the reviewer
#9628
2026-10-20 19:29
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The identity helper now computes an HMAC signature from the user's email and optional external ID. The app layout passes the signature to Plain Chat. Plain Chat includes it during initialization and sends it with customer details when updating the initialized widget. Tests cover missing-secret handling and signature values for email-only and email-plus-external-ID inputs. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to bb8ce No actionable issue remains in the Plain Chat identity change. It is mergeable after normal checks. Security architecture risk: 🔵 Low · up to bb8ce The change strengthens identity bin
… the rest (6442 characters)ding by signing both email and user ID while keeping the signing secret server-side. No introduced security defect was established. Compatibility with Plain’s live identity-update behavior remains unverified. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The directly affected authority is customer identity in the configured Plain application. The submitted signed identity contains an individual user’s email and external ID, not a tenant identifier. The supplied evidence does not establish the full conversation or customer-data access granted downstream by Plain. **Trust Boundaries and Controls** - _observed_ — The signing secret remains on the server. The browser receives the completed bearer signature and corresponding customer identity, then hands them to Plain. Signing now covers externalId as well as email rather than leaving the external ID outside the signed message. **Resilience and Maintainability Implications** - _observed_ — The local PlainInstance interface permits signature-bearing update calls, but it is not independent evidence of the external API’s acceptance, atomic replacement, or rejection behavior. The PR reports no live verification, leaving this security-contract boundary unresolved rather than demonstrating an unsafe transition. - _observed_ — Absent customer identity causes the identity effect to skip updates, and unmount closes the widget without explicitly clearing identity or canceling a pending script load. These lifecycle limitations are present at both base and head; whether Plain internally clears or retains identity remains unverified, and no increased exposure from this PR was established. **Hardening Proposals** - _proposed_ — Validate Plain’s signature-aware initialization and account-switch contract against authoritative documentation or a controlled integration: confirm identity and signature replacement together, rejection without stale-identity retention, and the required identity-clearing behavior on logout. Pre-merge checks: 5 · 2 Failed checks: 2 warnings | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Breaking Changes Match The Diff | | The checkbox is unticked, but the diff changes the Plain widget API request shape. In `apps/web/app/plain/components/plain-chat.tsx`, the `emailHash` field is removed from `customerDetails` and `custo… | Tick `- [x] This PR contains breaking changes` in the PR description and document the Plain widget request-shape change. | | Coverage Accounts For The Whole Diff | | The Coverage table covers the changed signing, no-secret, and widget init/update behaviours. However, its mutation row says to mutate `identity.ts:25`. In the reviewed head, line 25 is only a comment;… | Update the mutation row to name the actual message construction, for example `identity.ts:26-27` (or the exact line a reviewer should edit), while keeping `identity.test.ts` and the observed outcome. Then rerun the listed Vitest command. | Passed checks: 5 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title uses the required Conventional Commits format and clearly describes signing the Plain chat external ID. | | Description check | | The description includes the required ticket, change summary, affected files, breaking-change assessment, migration notes, test coverage, open gaps, and risks. | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Only The Ticket Line Carries A Magic Word | | The description contains one magic-word pair: “Fixes ENG-3943” on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears with an `ENG-<number>` or `#<number>` refere… | Full details: Only The Ticket Line Carries A Magic Word **Explanation** The description contains one magic-word pair: “Fixes ENG-3943” on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears with an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Breaking Changes Match The Diff **Explanation** The checkbox is unticked, but the diff changes the Plain widget API request shape. In `apps/web/app/plain/components/plain-chat.tsx`, the `emailHash` field is removed from `customerDetails` and `customerDetailsSignature` is added to `PlainInitOptions` at lines 48-49. The changed request is emitted on initialization at lines 102-103 and on identity updates at line 142. This matches the check's breaking-change rule for removed and added request fields. **Resolution** Tick `- [x] This PR contains breaking changes` in the PR description and document the Plain widget request-shape change. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table covers the changed signing, no-secret, and widget init/update behaviours. However, its mutation row says to mutate `identity.ts:25`. In the reviewed head, line 25 is only a comment; the signed message is built on lines 26–27. That location cannot be edited to turn the test red. **Resolution** Update the mutation row to name the actual message construction, for example `identity.ts:26-27` (or the exact line a reviewer should edit), while keeping `identity.test.ts` and the observed outcome. Then rerun the listed Vitest command. Comment `@the reviewer help` to get the list of available commands.
#9628
2026-10-20 19:31
cubic
review-body
—summary
**No issues found** across 4 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9628
2026-10-20 19:34
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No blocking issues were found, and this review supports merging with the author's acknowledged live-integration limitations. Reviews (1) · Last reviewed commit: "fix: sign Plain chat external ID with cu..." · Reviewed by the reviewer
#9567
2026-10-20 19:35
coderabbit
issue-comment
—summary
Priority: ⬆️ High Merge Risk: 🔵 Low · up to 992bf Data retention adds scheduled deletion, archiving and member deactivation behind an off-by-default licence. The remaining issues are minor. History can occasionally credit the policy with deleting a survey that a person archived by hand after restoring it. The health check's lookup of the latest sweep is not indexed. One integration test fails on databases that do not use UTC. The PR is mergeable with these follow-ups. Pre-merge checks: 5 · 1 · 1 Failed checks: 1 warning · 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :------------------------------------------------------
… the rest (6223 characters)--------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Coverage Accounts For The Whole Diff | | The visible Coverage table contains mutation rows without the required mutated `file:line`. “Purge never deletes a held survey” names `purge-eligibility.ts` without a line; the changed predicate is at… | Add the exact mutated `file:line` to each mutation row, for example `apps/web/modules/survey/archive/lib/purge-eligibility.ts:16`, `apps/web/modules/auth/lib/auth.ts:461`, `apps/web/modules/survey/archive/lib/retention-restart.ts:52`, and `… | | Only The Ticket Line Carries A Magic Word | | The visible description contains the allowed ticket line, `Fixes ENG-3713, ENG-3610, ENG-3611, ENG-3612, ENG-3721`. The supplied description is truncated after the screenshot section, and no full PR b… | Provide the complete raw pull request description, then scan every line for `Fixes\|Closes\|Resolves\|Ref` next to `ENG-<number>` or `#<number>`. Keep only the top ticket line unchanged; rewrite any later ticket ID as `ENG-…` or remove the mag… | Passed checks: 5 passed | Check name | Status | Explanation | | :----------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses the valid feat type, has a lowercase descriptive summary, and includes Linear references. | | Description check | | The description includes the required ticket, What & why, Where to look, Breaking changes, Migrations & env, testing coverage, Open gaps, Risks, and AI note sections. It clearly describes the user-vis… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description declares no breaking changes with an unticked checkbox. The authoritative diff contains no change matching the check: SDK packages are unchanged; no environment or chart configurati… | Full details: Description check **Explanation** The description includes the required ticket, What & why, Where to look, Breaking changes, Migrations & env, testing coverage, Open gaps, Risks, and AI note sections. It clearly describes the user-visible changes and validation evidence. Full details: Breaking Changes Match The Diff **Explanation** The PR description declares no breaking changes with an unticked checkbox. The authoritative diff contains no change matching the check: SDK packages are unchanged; no environment or chart configuration changes exist; existing v3 lifecycle responses retain the same `{ id, status, archivedAt }` acknowledgement and status handling; the new routes and problem codes are additive; and migrations add new tables plus nullable `User.reactivatedAt` and `User.lastActiveAt` columns with a backfill. The changed internal function signatures and workspace-package exports are outside the defined breaking-change scope. Full details: Coverage Accounts For The Whole Diff **Explanation** The visible Coverage table contains mutation rows without the required mutated `file:line`. “Purge never deletes a held survey” names `purge-eligibility.ts` without a line; the changed predicate is at `apps/web/modules/survey/archive/lib/purge-eligibility.ts:16-18`. “Members clock moves only after the second factor” names the test and describes plugin ordering, but gives no `file:line`; the registration is at `apps/web/modules/auth/lib/auth.ts:461`. “Restore restarts only policy-archived surveys' clocks” gives no `file:line`; the guard is at `apps/web/modules/survey/archive/lib/retention-restart.ts:52`. “AuthZed delivery never waits on other jobs” gives only “back on `background-jobs`”; the queue assignment is changed at `packages/jobs/src/recurring.ts:79`. The cleanup row does include `surveys.ts:68` and does not have this defect. The supplied authored description is truncated, and no complete description artifact is available locally, so later Open gaps content could not be independently checked; this does not remove the explicit mutation-row failures. **Resolution** Add the exact mutated `file:line` to each mutation row, for example `apps/web/modules/survey/archive/lib/purge-eligibility.ts:16`, `apps/web/modules/auth/lib/auth.ts:461`, `apps/web/modules/survey/archive/lib/retention-restart.ts:52`, and `packages/jobs/src/recurring.ts:79`. Then verify that the complete authored section contains `Open gaps` with `none` or the remaining uncovered behaviors and reasons. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The visible description contains the allowed ticket line, `Fixes ENG-3713, ENG-3610, ENG-3611, ENG-3612, ENG-3721`. The supplied description is truncated after the screenshot section, and no full PR body is available in the repository refs. A whole-description scan cannot confirm that no additional `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears later. **Resolution** Provide the complete raw pull request description, then scan every line for `Fixes|Closes|Resolves|Ref` next to `ENG-&lt;number&gt;` or `#&lt;number&gt;`. Keep only the top ticket line unchanged; rewrite any later ticket ID as `ENG-…` or remove the magic word. > [!WARNING] > ⚠️ This pull request shows signs of AI-generated slop (redundant_comments). It has been flagged by the reviewer slop detection and should be reviewed carefully. Comment `@the reviewer help` to get the list of available commands.
#9567
2026-10-20 19:48
cubic
review-body
—summary
**31 issues found** across 302 files **Note:** This PR contains a large number of files. the reviewer selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9567
2026-10-20 19:48
cubic
inline · P1
apps/web/modules/storage/service.ts:234defect-data
`deleteFilesByPrefix` returns `ok` even after S3 reports per-object delete errors, so this returns `true` and the cleanup drain drops the job without retrying failed objects. Propagate partial-delete failures from the storage helper before reporting success here.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/lib/utils/map-with-concurrency.ts:18hardening
A zero or negative limit starts no workers, so non-empty input resolves to an array of holes without invoking `fn`. Reject invalid limits before starting workers. ```suggestion if (items.length > 0 && (!Number.isInteger(limit) || limit < 1)) { throw new RangeError("limit must be a positive integer"); } await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker)); ```
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/entitlements/lib/checks.ts:17defect-functional
This mapping makes the shared guard reject licensed self-hosted organizations during the grace window, even when `dataRetention` remains enabled. Have the guard use `licenseActive` for self-hosted contexts so these organizations retain access.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/lib/survey-retention.ts:72defect-functional
For an unheld archived survey, this still reports the response-policy date and due count even though the archive purge deletes the survey and its responses after 30 days. Reconcile the response plan with the earlier purge deadline so the status does not promise retention beyond actual deletion.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/app/api/internal/retention-runs/export/lib/csv-stream.ts:105defect-functional
`signal.aborted` is checked only before this 500-row loop. An abort during `rows.next()` can let the loop drain the rest of the batch and record success. Recheck the signal after each awaited read before handling `next.done`. ```suggestion const next = await rows.next(); if (signal.aborted) { controller.close(); await stop("aborted"); return; } ```
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ui/components/input-combo-box/index.tsx:492defect-functional
Remote-search mode leaves prior-query options visible and selectable while the next search is pending. Suppress stale options until the new results arrive, or otherwise prevent them from being selected.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/lib/health-service.ts:27defect-functional
This treats any BullMQ consumer as a retention consumer. A custom external worker that omits the dedicated `data-retention` queue will suppress `jobsNotConfigured` even though the sweep cannot run; check retention-queue availability separately.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/components/policies/retention-policies-view.tsx:107defect-concurrency
The Edit button stays active while a switch mutation is pending, allowing a dialog save to overlap it. Their full-document responses can arrive out of order and leave the table showing stale settings; disable Edit while `pendingPolicy` is non-null.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/lib/policy-form.ts:30defect-functional
Arbitrary selector strings pass this schema: `Number("bogus")` yields `NaN`, so the range and notice-versus-period checks all pass. Restrict both selectors to their preset values plus `CUSTOM`, or reject non-finite conversions before submit.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/organization/settings/teams/lib/reactivate-member.ts:45defect-concurrency
A concurrent membership removal can commit after this query, allowing the function to reactivate an account after the target organization has removed it. Lock and recheck the target membership in this transaction before changing `isActive`.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/survey/archive/lib/retention-restart.ts:55defect-concurrency
With an expired, unrevoked exemption, this update can deadlock against `createRetentionExemption`: restore holds the survey lock while the API locks the exemption and then waits on the survey foreign key. Lock the survey before the API closes expired exemptions to keep the lock order consistent.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/components/exemptions/retention-exemptions-view.tsx:175defect-functional
names defect s1-new-exemption-hidden-by-failed-reload-1
A failed first-page refresh is silent whenever cached exemptions remain, and the footer only exposes next-page errors. After creating an exemption, a failed invalidation refetch can leave the new row absent with no retry control; show a refresh-error/retry affordance while retaining the rows.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/sweep/notices.ts:48defect-concurrency
For clock-bound targets, this insert never checks `clockAt` against the live target; with no existing notice, it claims the stale snapshot unconditionally. Activity between candidate collection and claim can therefore trigger an email with an outdated action date, even when the target is no longer due. Re-read and lock the target before claiming.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/auth/lib/oauth-grant-revocation.ts:79defect-security
This revokes only tokens already stored, so an authorization code issued before the transaction can still be exchanged afterward; the hook checks consent only for `refresh_token` grants. That exchange can mint a fresh access JWT after SSO recovery; invalidate pending codes or reject code redemption for revoked users.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/components/policies/policy-edit-dialog.tsx:436defect-functional
The active-policy guidance isn't associated with the switch, so screen readers won't announce what activating the policy does. Give the help text an id and reference it with the switch's `aria-describedby`.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/hooks/use-retention-policies.ts:32defect-functional
A PATCH can commit even when its response is lost, but this error handler refreshes only the policy document and leaves health banners cached against the old enabled state. Invalidate `retentionHealthKeys.detail(organizationId)` on errors too.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/lib/response/delete-responses.ts:37defect-concurrency
names defect s17-file-saved-during-delete-left-in-storage-1
This reads response data before the later delete, so a concurrent `patch.data` update can make the helper delete the updated row while cleanup queues only its old file URLs. Lock the rows while collecting their data or delete-and-return them atomically, so newly attached files are also cleaned up.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/components/survey/survey-retention-card.tsx:35defect-functional
names defect s8-retention-lookup-failure-hides-retention-card-1
A failed initial retention request also reaches this branch, so the card silently disappears as though the survey were ungoverned. Handle `isError` separately and show an error or retry state instead of hiding the card.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/app/api/internal/retention-runs/export/lib/operations.ts:81defect-concurrency
The stream can exceed the 100,000-row preflight cap because it reads a later database state than the count; an active sweep can add items after the count and before or during streaming. Pin the count and iteration to a fixed dataset snapshot or watermark so the guardrail covers every exported row.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/components/exemptions/add-exemption-dialog.tsx:185defect-functional
The `Until` label is disconnected from the date-picker button, whose accessible name is only the selected date or placeholder. Give the trigger an accessible name such as “Until,” for example through a `DatePicker` `aria-label` prop.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/lib/policy-rules.ts:75defect-functional
Rows with duplicate conditions can make `[a, a, b]` and `[a, b, b]` compare equal, so the sweep may miss a policy edit. Compare sorted arrays or occurrence counts instead.
#9567
2026-10-20 19:48
cubic
inline · P2
packages/database/migration/20261012120000_retention_notice_void_on_response_delete/migration.sql:54defect-concurrency
An in-flight sweep can overwrite this invalidation with the pre-change clock: it reads the survey clock before the response change, then claims the notice after this update commits using its cached `clockAt`. Revalidate the current clock under synchronization with response writes before claiming, so the sweep cannot send dates computed before the change.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/lib/exemption-form.ts:45defect-functional
`addYears` runs in the browser’s local zone here, but the API applies it to the database clock on the server; differing DST offsets can move the cutoff across an organization-local midnight. The picker can then offer an end date the API rejects with 422; calculate the bound from a shared, timezone-independent cutoff.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/sweep/members-sweeper.ts:168defect-security
A session-revocation error exits this `try` before OAuth cleanup, leaving grants and consent live even when Postgres is available. Attempt both cleanups independently. ```suggestion const revocations = await Promise.allSettled([ revokeUserSessionsExcept({ userId }), prisma.$transaction((tx) => revokeAllUserOAuthGrants(tx, userId)), ]); const failed = revocations.find((result) => result.status === "rejected"); if (failed?.status === "rejected") throw failed.reason; ```
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/sweep/members-sweeper.ts:165defect-concurrency
The active-state check is not synchronized with reactivation: a reactivation can commit after this read but before cleanup, so the newly active member still loses their sessions and OAuth grants. Serialize cleanup with the same user-row lock used by reactivation.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/sweep/surveys-sweeper.ts:73defect-concurrency
This exemption check runs only during the initial scan; an exemption created before notice claiming is not rechecked, so the owner can receive an archive date while the survey is held. Revalidate active exemptions when claiming the notice.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/modules/ee/data-retention/sweep/responses-sweeper.ts:147defect-data
This audit reports `responseIds.length` as deleted, but the helper's `deletedIds` are rows read before deletion and can include a response concurrently deleted by a person. Pass the helper's authoritative `deleted` count to `auditDeletion` and avoid presenting those read IDs as confirmed deletes.
#9567
2026-10-20 19:48
cubic
inline · P2
apps/web/locales/pt-PT.json:3167docs
This says there is nothing to delete, although the status means no action is due yet and also covers survey archiving. Translate it as a generic status, such as “ainda não há nenhuma ação prevista.” ```suggestion "survey_line_nothing_due": "{policy}: ainda não há nenhuma ação prevista.", ```
#9567
2026-10-20 19:48
cubic
inline · P3
apps/web/locales/nl-NL.json:3156docs
Deze zin is grammaticaal onjuist en laat onduidelijk waarop de nieuwe uitzondering betrekking heeft. Gebruik bijvoorbeeld: “Je kunt er op elk moment een nieuwe uitzondering voor toevoegen.” ```suggestion "revoke_exemption_description": "Je kunt er op elk moment een nieuwe uitzondering voor toevoegen.", ```
#9567
2026-10-20 19:48
cubic
inline · P3
apps/web/locales/pt-BR.json:3156docs
“Para ele” refers to the survey, which is feminine in Portuguese, and “exceção” is inconsistent with the feature’s “isenção” terminology. Use “para ela” and “isenção”. ```suggestion "revoke_exemption_description": "Você pode adicionar uma nova isenção para ela a qualquer momento.", ```
#9567
2026-10-20 19:48
cubic
inline · P3
apps/web/locales/es-ES.json:3106docs
`{name} reactivado` assumes every member has a masculine name, so the success toast is grammatically incorrect for many users. Use a gender-neutral construction such as `Se ha reactivado a {name}`. ```suggestion "member_reactivated": "Se ha reactivado a {name}", ```
#9567
2026-10-20 19:49
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Critical impact]** The PR reaches stored response data\, survey records\, uploaded files\, and member access\. The confirmed access-recovery and display-date issues are non-blocking; neither establishes unauthorized access or premature archival. Findings 1. **Reactivation loses new access** 2. **Scheduled surveys show wrong dates** Reviews (1) · Last reviewed commit: "feat: data retention policies, exemption..." · Reviewed by the reviewer
#9567
2026-10-20 19:49
greptile
inline · P2
apps/web/modules/ee/data-retention/sweep/members-sweeper.ts:168defect-concurrency
**Reactivation loses new access** `revokeCredentials` checks `isActive` without holding the user lock through cleanup. An admin can reactivate the member after that check, and the member can sign in before `revokeUserSessionsExcept` reads their sessions. Cleanup then deletes the new session and can revoke new OAuth grants too. This is a non-blocking access-recovery issue: the member can be signed out immediately after reactivation and may need to reconnect OAuth clients. Serialize the inactivity decision and cleanup against reactivation, or exclude credentials issued after reactivation. Artifacts **Reactivation race test and existing execution evidence** - Archived and uploaded the six
… the rest (148 characters)requested existing files without executing them; S3 confirmed receipt with HTTP 200 and a matching content ETag. Ran code and verified through TREX
#9567
2026-10-20 19:49
greptile
inline · P2
apps/web/modules/ee/data-retention/lib/survey-retention.ts:135defect-functional
**Scheduled surveys show wrong dates** `getSurveysPlan` reports an archive date without considering `publishOn`, while the sweep skips surveys whose launch is still in the future. An old survey under a `createdBefore` policy can therefore show an archive date before launch, even though its notice will not be sent until launch. This is a non-blocking display issue, not premature archival. Carry `publishOn` into the retention facts and apply the same launch guard when calculating the date, including the full notice after that guard clears. Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! Artifacts **Executed te
… the rest (881 characters)st, source, commands, and observed evidence** - Uploaded the authored test, configuration, runner, captured source, calculation results, UI harness, screenshots, and execution records; S3 confirmed receipt with HTTP 200 and a matching ETag. **Passing retention calculation execution record** - Uploaded the existing test output with command, working directory, and exit code; the focused runtime test passed without being rerun. **Retention card before scheduling a future launch** - Rendered the actual card using the unscheduled control fixture in Chromium; July 31 agrees with the earliest archive after full notice. **Retention card after scheduling a September launch** - Rendered the same card with the future-launch fixture in Chromium; it still says July 31 despite the September launch and November notice-complete archive bound. Ran code and verified through TREX
#9567
2026-10-20 19:54
coderabbit
inline · P2
apps/web/modules/ee/data-retention/lib/health-service.ts:25defect-performance
**Index `RetentionRun.kind` for the health lookup.** `getRetentionHealthFacts` filters `RetentionRun` by `organizationId` and `kind: "sweep"`, then orders by `startedAt`. The current index does not include `kind`, so PostgreSQL may scan many non-sweep runs before finding the latest sweep. Suggested schema change ```diff @@index([organizationId, startedAt, id]) + @@index([organizationId, kind, startedAt]) ``` The `DeletionCleanup` lookup is already covered by its existing `(organizationId, createdAt)` index. _Source: Path instructions_
#9567
2026-10-20 19:54
coderabbit
inline · P2
apps/web/modules/ee/data-retention/lib/policies-service.integration.test.ts:127tests
**Read the bounds with `readDatabaseClock`, not raw `clock_timestamp()`.** `readDatabaseClock` in `apps/web/lib/utils/database-clock.ts` documents that `@prisma/adapter-pg` relabels a `timestamptz` offset as `+00:00` without converting it. Line 120 and Line 127 read `clock_timestamp()` as a `timestamptz`. On a database whose session time zone is not UTC, `before` and `after` are therefore shifted by that offset. `enabledAt` is stamped through `readDatabaseClock`, which is not shifted, so the range assertion on Line 132 and Line 134 fails by hours. The doc comment says initdb copies the host's time zone. A developer who runs the integration suite against a local Postgres on a non-UTC host t
… the rest (1091 characters)herefore gets a failing test, while CI on UTC passes. Proposed fix ```diff --- "a/apps/web/modules/ee/data-retention/lib/policies-service.integration.test.ts" +++ "b/apps/web/modules/ee/data-retention/lib/policies-service.integration.test.ts" @@ -117,14 +117,14 @@ }); test("stamps enabledAt from the database clock, read after the row lock, when no time is given", async () => { - const before = (await prisma.$queryRaw<{ now: Date }[]>`SELECT clock_timestamp() AS "now"`)[0].now; + const before = await readDatabaseClock(prisma); await updateRetentionPolicy({ organizationId, policy: "members", patch: { enabled: true }, updatedById: userId, }); - const after = (await prisma.$queryRaw<{ now: Date }[]>`SELECT clock_timestamp() AS "now"`)[0].now; + const after = await readDatabaseClock(prisma); const { enabledAt } = await prisma.retentionPolicy.findUniqueOrThrow({ where: { organizationId_entity: { organizationId, entity: "members" } }, ``` Add `import { readDatabaseClock } from "@/lib/utils/database-clock";`.
#9567
2026-10-20 19:54
coderabbit
inline · P3
apps/web/modules/organization/settings/teams/components/edit-memberships/reactivate-member-button.tsx:40design
**Track the member-list query migration and invalidate it after reactivation.** `EditMemberships` fetches memberships on the server and passes them to `MembersInfo`, which renders those props directly. Its `QueryClient` currently supports mutations, not a member-list query. This `router.refresh()` is therefore a data-refresh mechanism covered by the `.tsx` contract. The refresh currently keeps the server-rendered status in sync, so this is not a user-facing failure. Track the migration of the member list to TanStack Query. After that migration, invalidate the member-list query key in `onSuccess` and remove `router.refresh()`. Delete and role-change actions also use `router.refresh()`, so i
… the rest (46 characters)nclude them in the migration where applicable.
#9567
2026-10-20 19:54
coderabbit
inline · P2
apps/web/modules/survey/archive/lib/retention-purge-history.ts:39defect-functional
**Count only policy archives made after the survey's last restore.** `archivedByPolicy` matches any `archived` row for the survey, however old. `restartSurveyRetention` applies a narrower rule: an archive counts only if it is in a `sweep` run that started after the last `RetentionSurveyRestore.restoredAt`. Trigger: the policy archives a survey, someone restores it, and a person later archives it by hand. The purge then writes a `deleted` row and bumps `deletedCount` in a purge run, so History shows the policy deleting a survey a person archived. The docblock says a survey archived by hand "stays out of History", which this case breaks. Proposed fix ```diff const rows = await prism
… the rest (1607 characters)a.$queryRaw<{ id: string }[]>` SELECT DISTINCT i."targetId" AS "id" FROM "RetentionRunItem" i JOIN "RetentionRun" r ON r."id" = i."runId" + LEFT JOIN "RetentionSurveyRestore" rs ON rs."surveyId" = i."targetId" WHERE i."targetId" = ANY(${surveyIds}::text[]) AND i."targetType" = 'survey' - AND i."action" = 'archived' AND r."entity" = 'surveys' + AND i."action" = 'archived' AND r."entity" = 'surveys' AND r."kind" = 'sweep' + AND r."startedAt" > COALESCE(rs."restoredAt", '-infinity'::timestamp) `; ``` 📝 Committable suggestion > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion async archivedByPolicy(surveyIds: readonly string[]): Promise<Set<string>> { if (surveyIds.length === 0) return new Set(); const rows = await prisma.$queryRaw<{ id: string }[]>` SELECT DISTINCT i."targetId" AS "id" FROM "RetentionRunItem" i JOIN "RetentionRun" r ON r."id" = i."runId" LEFT JOIN "RetentionSurveyRestore" rs ON rs."surveyId" = i."targetId" WHERE i."targetId" = ANY(${surveyIds}::text[]) AND i."targetType" = 'survey' AND i."action" = 'archived' AND r."entity" = 'surveys' AND r."kind" = 'sweep' AND r."startedAt" > COALESCE(rs."restoredAt", '-infinity'::timestamp) `; return new Set(rows.map((row) => row.id)); }, ```
#9567
2026-10-20 19:54
coderabbit
review-body
—summary
**Actionable comments posted: 4**
#9619
2026-10-20 19:56
coderabbit
issue-comment
—summary
### Walkthrough `getIsActiveCustomer` now logs organization lookup errors with the error and user ID, then returns `false`. Successful lookup behavior remains unchanged: paid plans with active or trialing subscriptions return `true`, and self-hosted instances return `false` without querying organizations. A test verifies that a rejected organization lookup resolves to `false` and logs once. Priority: ⬇️ Low Merge Risk: 🔵 Low · up to c3326 Lookup failures can expose sensitive diagnostic details in application logs; sanitize the logged error before merging. Security architecture risk: 🔵 Low · up to c3326 The change contains an optional chat-label failure without changing authorization
… the rest (5814 characters)or billing entitlements. A low-severity concern remains because lookup errors and user identifiers are newly sent to logs without an explicit data-minimization control. No direct browser disclosure or privilege escalation was established. **Retained concerns** - **Low · security · inferred:** The new failure handler forwards a raw lookup error and stable user identifier across the operational-logging boundary without an allowlisted diagnostic schema or configured redaction. Exposure depends on the error's serializable fields and log readership; credential or cross-tenant disclosure was not demonstrated. Security review details **Security Blast Radius** - _inferred_ — The established exposure is server-side logging for configured cloud application layouts. Each event includes the current user's identifier; a deployment-wide outage could generate events for many users. Independently attacker-triggered failures, attacker-controlled error payloads, access to logs, and cross-tenant data disclosure were not established. **Security Findings and Attack Paths** - _observed_ — The retained low-severity sensitive-data finding concerns lookup failure propagation into logger.error({ error, userId }). This explicit structured logging call is new relative to the base helper. The configured err serializer does not apply to the error field, and the logger has no configured redaction; the exact emitted diagnostic content remains error-type dependent. **Trust Boundaries and Controls** - _inferred_ — The material boundary is application diagnostics entering operational logs, not an authentication bypass. Session-derived identity and authorized organization selection remain in place. Authorization-error normalization and standard Error message non-enumerability limit typical payloads, but do not constitute general redaction for every error the helper can catch. **Resilience and Maintainability Implications** - _inferred_ — Converting classification failure to false withholds the optional label rather than granting a paid entitlement. The inspected fallback does not change organization ownership or authorization state, so partial failure requires no persistent-state recovery in this path. **Hardening Proposals** - _proposed_ — Prefer an allowlisted diagnostic code and only the correlation information needed for operations. Apply explicit sanitization before logging error details; changing the field name to err alone would not provide redaction. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses a valid type and scope, describes the fail-open lookup change, uses lowercase summary text, and includes the ENG-2117 reference. | | Description check | | The description is complete and directly matches the changes. It includes the ticket reference, behavior before and after, code location, breaking-change status, migration impact, test coverage, known… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR marks breaking changes as unticked. The diff only updates the internal `getIsActiveCustomer` helper and its unit test in `apps/web/app/plain/lib/customer.ts` and `customer.test.ts`. It does not… | | Only The Ticket Line Carries A Magic Word | | The only magic-word pair in the PR description is `Fixes ENG-2117` on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears next to an `ENG-<number>` or `#<number>`… | | Coverage Accounts For The Whole Diff | | The diff adds one error-handling behavior: organization lookup failures return false and log once. The Coverage table names this behavior, the exact unit test, and the observed result. It also covers … | Full details: Description check **Explanation** The description is complete and directly matches the changes. It includes the ticket reference, behavior before and after, code location, breaking-change status, migration impact, test coverage, known gaps, and risks. Full details: Breaking Changes Match The Diff **Explanation** The PR marks breaking changes as unticked. The diff only updates the internal `getIsActiveCustomer` helper and its unit test in `apps/web/app/plain/lib/customer.ts` and `customer.test.ts`. It does not change an API shape, route, HTTP status, configuration key, migration, or public SDK signature. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The only magic-word pair in the PR description is `Fixes ENG-2117` on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Coverage Accounts For The Whole Diff **Explanation** The diff adds one error-handling behavior: organization lookup failures return false and log once. The Coverage table names this behavior, the exact unit test, and the observed result. It also covers the existing paid, trial, hobby, and self-hosted cases with the named customer test suite and reports 10/10 passing. The Rerun command is present for the red-on-main claim. The Open gaps subsection is present, and the diff changes only server-side logic and tests, not rendered UI code. Comment `@the reviewer help` to get the list of available commands.
#9619
2026-10-20 19:56
cubic
review-body
—summary
**1 issue found** across 2 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9619
2026-10-20 19:56
cubic
inline · P2
apps/web/app/plain/lib/customer.ts:35defect-functional
Pino only applies its error serializer to `err`, so this logs a typical `Error` under `error` without its message or stack. Pass it as `err` to retain the lookup failure details. ```suggestion logger.error({ err: error, userId }, "Failed to resolve Plain active-customer status"); ```
#9619
2026-10-20 20:00
coderabbit
inline · P2
apps/web/app/plain/lib/customer.ts:35defect-security
**🔒 Security & Privacy** | **🛡️ Detected with Advanced Tier** | **🟡 Minor** | **⚡ Quick win** **Sensitive Data Exposure** **Reachability:** Internal **CWE:** CWE-532 — Insertion of Sensitive Information into Log File **Do not log the raw lookup error.** The logger has no redaction setting, and this call passes the caught value under `error`, not `err`. `ResourceNotFoundError` can carry `resourceId`, while database errors retain the database message. Replace the raw error object with a sanitized, length-limited diagnostic before logging. _Source: Learnings_
#9619
2026-10-20 20:00
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9619
2026-10-20 20:00
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The change reaches authenticated app pages when the Plain customer label is configured\. Safe to merge with one non-blocking diagnostic concern: the fallback works, but its log loses the exception message and stack. Findings 1. **Logs lose error details** Reviews (1) · Last reviewed commit: "fix(plain): fail open when the active-cu..." · Reviewed by the reviewer
#9619
2026-10-20 20:01
greptile
inline · P2
apps/web/app/plain/lib/customer.ts:35defect-functional
**Logs lose error details** `logger.error` receives the exception under `error`, but the logger’s error serializer uses `err`. A failed lookup with `DatabaseError` therefore logs its name and status code without its message or stack. The fallback still works, so this is a non-blocking diagnostic concern: operators have less information to explain missing customer labels. Use `{ err: error, userId }` to preserve those details. ```suggestion logger.error({ err: error, userId }, "Failed to resolve Plain active-customer status"); ``` Artifacts **Executable production logger and helper validation script** - Runs the real production logger and transpiled helper with a real DatabaseError,
… the rest (588 characters)captures stdout, and asserts before-and-after serialization without modifying tracked source. **Production JSON with the unchanged error property** - Executed direct logging and the unmodified helper catch path with a rejected lookup; both JSON records omit the exception message and stack, confirming the finding. **Production JSON with the in-memory err property correction** - Repeated the same flows with only an in-memory error-to-err correction; both JSON records preserve the exception message and stack while the helper still returns false. Ran code and verified through TREX
#9625
2026-10-20 20:04
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The change adds utilities to remove translated values and make a selected language the sole enabled default. The language editor uses these utilities when disabling translations or changing the default. Non-default language menus now include a “Make default” action. Tests cover translation cleanup, metadata handling, input immutability, and promotion of a language absent from the survey. English translations were added for the related actions and requirement. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to c0b46 The language actions appear to preserve the intended behavior. No actionable merge blocker remains
… the rest (5987 characters)after normal checks. Security architecture risk: ⚪ Minimal · up to c0b46 The new language actions preserve existing default content, require confirmation before deleting translations, and use the existing authorized survey-save flow. No material security risk introduced or worsened by this change was identified. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The new operation's direct effect is confined to the selected survey's language configuration and translated content. Persisting that effect requires the existing survey-write authority; the change does not establish additional cross-workspace reachability or privilege. **Trust Boundaries and Controls** - _observed_ — The selected language and full survey payload remain client-controlled inputs. The new menu does not persist them directly or replace server authorization: the existing authenticated action validates the survey and enforces survey-write permission before calling the persistence service. Pre-merge checks: 6 · 1 Failed checks: 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Only The Ticket Line Carries A Magic Word | | The visible description contains only the allowed ticket line, “Fixes ENG-3617”. However, the supplied description is explicitly marked as truncated, and the repository does not store the full PR desc… | Provide the complete raw PR description. Rescan every occurrence of `Fixes`, `Closes`, `Resolves`, or `Ref` followed by `ENG-<number>` or `#<number>` outside the top ticket line. | Passed checks: 6 passed | Check name | Status | Explanation | | :---------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses the allowed fix type and scope, describes the default-language change, uses lower-case summary text, has no trailing period, and includes the ENG-36… | | Description check | | The description includes the required ticket reference, What & why, Where to look, Breaking changes, Migrations & env, testing coverage, Open gaps, Risks, and agent note. It clearly explains the user-… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR declares no breaking changes with an unticked checkbox. The diff changes only web UI behavior, locale files, the i18n lockfile, tests, and internal survey utilities. It does not change an API s… | | Coverage Accounts For The Whole Diff | | The description includes a non-empty Coverage table with six rows. Each row states a verification method and observed outcome. Unit rows name `utils.test.ts` through the Rerun command and identify mut… | Full details: Title check **Explanation** The title follows Conventional Commits format, uses the allowed fix type and scope, describes the default-language change, uses lower-case summary text, has no trailing period, and includes the ENG-3617 ticket reference. Full details: Description check **Explanation** The description includes the required ticket reference, What & why, Where to look, Breaking changes, Migrations & env, testing coverage, Open gaps, Risks, and agent note. It clearly explains the user-visible behavior and reports the known mutation-test failure and manual UI checks. Full details: Breaking Changes Match The Diff **Explanation** The PR declares no breaking changes with an unticked checkbox. The diff changes only web UI behavior, locale files, the i18n lockfile, tests, and internal survey utilities. It does not change an API shape, route, HTTP status, environment/config key, database schema or migration, or the public SDK surface. The new exports in `apps/web/modules/survey/multi-language-surveys/lib/utils.ts` are internal modules and are outside this check. Full details: Coverage Accounts For The Whole Diff **Explanation** The description includes a non-empty Coverage table with six rows. Each row states a verification method and observed outcome. Unit rows name `utils.test.ts` through the Rerun command and identify mutation edit points at `utils.ts:353` and `utils.ts:366`. The rows cover translation removal, metadata preservation, default promotion, menu changes, dialog copy, and the confirmed German flow. The rendered UI change includes screenshots. The `Open gaps` subsection is present and explains the absence of e2e coverage. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The visible description contains only the allowed ticket line, “Fixes ENG-3617”. However, the supplied description is explicitly marked as truncated, and the repository does not store the full PR description. The whole raw body is required for this check. **Resolution** Provide the complete raw PR description. Rescan every occurrence of `Fixes`, `Closes`, `Resolves`, or `Ref` followed by `ENG-&lt;number&gt;` or `#&lt;number&gt;` outside the top ticket line. Comment `@the reviewer help` to get the list of available commands.
#9625
2026-10-20 20:12
cubic
inline · P2
apps/web/locales/nl-NL.json:3937docs
This new action makes the existing default-language warning inaccurate: it says the default can only change after disabling multi-language, but this action keeps it enabled. Update that warning to describe both paths.
#9625
2026-10-20 20:12
cubic
inline · P3
apps/web/locales/ro-RO.json:3937docs
The language-row action uses the masculine form “implicit,” which does not agree with “limbă.” Use “Setează ca limbă implicită” to make the Romanian label grammatical and explicit. ```suggestion "make_default": "Setează ca limbă implicită", ```
#9625
2026-10-20 20:12
cubic
inline · P3
apps/web/locales/ru-RU.json:4036docs
The Russian confirmation button is grammatically incomplete: `Удалить и изменить основной` leaves `основной` without a noun. Name the object explicitly, for example `Удалить и изменить язык по умолчанию`, so the destructive action clearly refers to the default language. ```suggestion "remove_and_change_default": "Удалить и изменить язык по умолчанию", ```
#9625
2026-10-20 20:12
cubic
review-body
—summary
**3 issues found** across 19 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9625
2026-10-20 20:13
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The change is limited to survey language editing but can remove saved translation text\. No blocking issue was identified; the remaining metadata-preservation concern is non-blocking and does not by itself make the change unsafe to merge. Findings 1. **Preserve nontranslation logic metadata** Reviews (1) · Last reviewed commit: "fix(survey-editor): clarify changing a s..." · Reviewed by the reviewer
#9625
2026-10-20 20:13
greptile
inline · P2
apps/web/modules/survey/multi-language-surveys/lib/utils.ts:366defect-data
**Preserve nontranslation logic metadata** If a survey’s logic operand metadata contains a string `default` key, this traversal treats the entire metadata record as translated text and deletes every other string-valued entry. Operand metadata permits arbitrary string keys, so changing the default or removing translations can silently discard valid, unrelated metadata. Both new helpers reproduced this deletion; the previous helper preserved the non-language keys. Restrict cleanup to translation fields or exclude logic metadata. This is a non-blocking data-preservation concern; no downstream logic-execution failure was established. Artifacts **Evidence from the check** - The saved script d
… the rest (1006 characters)irectly exercised the parent and changed helpers; it contains the reproducible metadata-preservation checks. **Evidence from the check** - The saved configuration resolved repository imports for the runtime checks; it is the configuration used in both captured runs. **Evidence from the check** - The saved parent source supplied the baseline helper used by the script; it makes the comparison reproducible. **Command output from the check** - The captured parent run includes its command, working directory, exit code, and resulting metadata; non-language fixture keys were preserved. **Command output from the check** - The captured changed-helper run includes its command, working directory, exit code, and assertion failures; non-language fixture keys were deleted. **Command output from the check** - The captured source evidence identifies the deletion loop and arbitrary metadata schema; it does not establish production consumption of the fixture keys. Ran code and verified through TREX
#9626
2026-10-20 20:15
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The rating component now uses a shared mapping to assign fill and ink classes to smiley faces. Tests cover the supported rating ranges and color-coding modes. The build assertion checks for `dark:text-slate-900` in the built stylesheet and JavaScript files, and reports missing utilities. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to 54a17 No actionable issue remains from this review; the change is ready for normal merge checks. Pre-merge checks: 6 · 1 Failed checks: 1 warning | Check name | Status | Explanation | Resolution | | :----------------------------- | :------------------------------------------
… the rest (4763 characters)----------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- | | Breaking Changes Match The Diff | | The Breaking changes checkbox is unticked, but the diff changes the exported `RatingProps` signature. In `packages/survey-ui/src/components/elements/rating.tsx:83`, `range` changes from the inline uni… | Tick `- [x] This PR contains breaking changes` and document the `RatingProps.range` signature change in the section. Do not leave the section as `None`. | Passed checks: 6 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Description check | | The description includes the required ticket reference, change summary, review locations, breaking-change status, migration notes, test coverage, open gaps, risks, and AI note. It is complete and dire… | | Title check | | The title follows the required Conventional Commits format, uses a valid type and scope, describes the dark-mode readability fix, uses lower-case summary text, has no trailing period, and includes a v… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Only The Ticket Line Carries A Magic Word | | The only magic-word pair in the supplied PR description is the ticket line `Fixes ENG-3937` at the top. No other occurrence of `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-<number>`… | | Coverage Accounts For The Whole Diff | | The description accounts for the changed behavior. The Coverage table has six rows with verification methods and observed outcomes for filled and unfilled ink, color mappings, built CSS/JS utilities, … | Full details: Description check **Explanation** The description includes the required ticket reference, change summary, review locations, breaking-change status, migration notes, test coverage, open gaps, risks, and AI note. It is complete and directly related to the changes. Full details: Title check **Explanation** The title follows the required Conventional Commits format, uses a valid type and scope, describes the dark-mode readability fix, uses lower-case summary text, has no trailing period, and includes a valid ENG ticket reference. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The only magic-word pair in the supplied PR description is the ticket line `Fixes ENG-3937` at the top. No other occurrence of `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Coverage Accounts For The Whole Diff **Explanation** The description accounts for the changed behavior. The Coverage table has six rows with verification methods and observed outcomes for filled and unfilled ink, color mappings, built CSS/JS utilities, dark-mode rendering, and light-mode stability. The unit rows name `smiley-colors.test.ts`; the mutation row names `smiley-colors.ts:29`. The rendered UI change includes four screenshot attachments. The Open gaps subsection is present and lists the mobile SDK and independent web copy gaps. Full details: Breaking Changes Match The Diff **Explanation** The Breaking changes checkbox is unticked, but the diff changes the exported `RatingProps` signature. In `packages/survey-ui/src/components/elements/rating.tsx:83`, `range` changes from the inline union `3 | 4 | 5 | 6 | 7 | 10` to `RatingRange`. `RatingProps` is part of the public package export at `packages/survey-ui/src/index.ts:50`. **Resolution** Tick `- [x] This PR contains breaking changes` and document the `RatingProps.range` signature change in the section. Do not leave the section as `None`. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 4 - Waived: $1.00 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9626
2026-10-20 20:21
cubic
inline · P2
packages/survey-ui/scripts/assert-scoped-css.mjs:81defect-functional
This can accept stale JS: Vite sets `emptyOutDir: false`, so a removed or moved module can remain in `dist` and satisfy this scan after a later build. Clean `dist` before building or restrict the check to artifacts emitted by the current build.
#9626
2026-10-20 20:21
cubic
review-body
—summary
**1 issue found** across 4 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9626
2026-10-20 20:27
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge within the reviewed scope. The rendered dark-mode correction works without changing face fills, unfilled dark faces, or light-mode rendering. Reviews (1) · Last reviewed commit: "fix(survey-ui): keep smiley rating line ..." · Reviewed by the reviewer
#9634
2026-10-20 20:28
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough Survey validation now detects cycles in jump, fallback, and sequential block flows. It applies the configured policy to report cycles, and adds resulting errors to document validation results. Regression tests cover newly introduced cycles, existing cycles, cycle-breaking patches, and blocks that lead into cycles. API documentation describes the validation behavior and affected logic paths. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to 586e5 This change makes survey PATCH, block edit, and block reorder requests reject newly introduced cyclic logic, matching survey creation. No concrete merge-blocking risk
… the rest (7613 characters)was identified in the reviewed changes. Security architecture risk: 🔵 Low · up to 586e5 The change rejects newly introduced survey loops before writes without expanding write permissions, while preserving compatibility for existing loops. Remaining uncertainty concerns resource behavior for unusually large stored surveys. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _observed_ — The changed validation is reached through the existing survey mutation path, which obtains the target survey with read-write access and rejects archived surveys before mutation preparation. It does not add a new route or grant additional authority. The routed test-file symbols are regression helpers, not production entrypoints. **Trust Boundaries and Controls** - _observed_ — Although the cycle graph uses block IDs as edge identity, duplicate IDs do not provide an acceptance bypass in the inspected whole-document PATCH path. Document validation also invokes reference validation, which records duplicate block IDs as errors; any accumulated error prevents persistence. **Resilience and Maintainability Implications** - _observed_ — Submitted block arrays are capped at 1,000, but stored documents deliberately bypass array caps. The new graph traversal is recursive and analyzes both candidate and baseline documents. This leaves resource behavior for unusually large stored surveys unverified; it is not an established denial-of-service finding. **Hardening Proposals** - _proposed_ — Consider iterative graph traversal so validation of uncapped stored surveys does not depend on JavaScript call-stack depth, while preserving the existing ability to repair oversized documents. Pre-merge checks: 4 · 2 · 1 Failed checks: 2 warnings · 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Breaking Changes Match The Diff | | The PR description leaves `- [ ] This PR contains breaking changes` unticked. The diff adds cyclic-logic invalid parameters at `apps/web/app/api/v3/surveys/validation.ts:327`. For existing v3 PATCH, b… | Tick the breaking-change checkbox in the PR description and document that cyclic-logic requests that previously succeeded now return HTTP 422 for survey PATCH, block edit, and block reorder. | | Coverage Accounts For The Whole Diff | | The Coverage table has two documentation gaps. The two `unit (mutation)` rows say `same mutation` and cite commit `e47bb4c`, but they do not name the mutated `file:line`. The `unit (guard)` row says o… | Update the mutation rows with the exact mutation target, such as `apps/web/app/api/v3/surveys/validation.ts:<line>` and the edit that makes each test fail. Replace `same describe block` with the exact test name, for example `prepare.test.ts… | | Only The Ticket Line Carries A Magic Word | | The visible description contains the expected ticket line `Fixes ENG-3946` and no other visible magic-word pair. However, the supplied description is explicitly truncated at `To reproduce on a deploye… | Provide the complete raw PR description, then scan all text for `Fixes\|Closes\|Resolves\|Ref` next to `ENG-` or `#`. PASS only if `Fixes ENG-3946` is the sole pair. | Passed checks: 4 passed | Check name | Status | Explanation | | :------------------------ | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows the Conventional Commits format, uses a valid lowercase type and scope, has no trailing period, and accurately describes the cyclic-logic validation change. | | Description check | | The description covers the required sections, explains the behavior change, links to the main code and specification areas, documents breaking-change status, migrations, test coverage, open gaps, and … | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | Full details: Description check **Explanation** The description covers the required sections, explains the behavior change, links to the main code and specification areas, documents breaking-change status, migrations, test coverage, open gaps, and risks. It is complete enough for review, although mutation coverage rows do not include mutated file and line references. Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves `- [ ] This PR contains breaking changes` unticked. The diff adds cyclic-logic invalid parameters at `apps/web/app/api/v3/surveys/validation.ts:327`. For existing v3 PATCH, block-edit, and block-order requests that introduce a loop, the shared patch path now raises validation failure and the existing error mapper returns HTTP 422 (`apps/web/app/api/v3/surveys/lib/operations.ts:809-817`). This changes the status code for an existing request case. **Resolution** Tick the breaking-change checkbox in the PR description and document that cyclic-logic requests that previously succeeded now return HTTP 422 for survey PATCH, block edit, and block reorder. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table has two documentation gaps. The two `unit (mutation)` rows say `same mutation` and cite commit `e47bb4c`, but they do not name the mutated `file:line`. The `unit (guard)` row says only `same describe block`, so it does not name a test or spec. The diff adds the relevant named tests in `apps/web/app/api/v3/surveys/prepare.test.ts` at lines 851 and 860, but the description does not identify them. **Resolution** Update the mutation rows with the exact mutation target, such as `apps/web/app/api/v3/surveys/validation.ts:&lt;line&gt;` and the edit that makes each test fail. Replace `same describe block` with the exact test name, for example `prepare.test.ts` test `keeps a survey that already loops patchable, and accepts the patch that breaks the loop`. Retain the observed outcomes and Open gaps entry. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The visible description contains the expected ticket line `Fixes ENG-3946` and no other visible magic-word pair. However, the supplied description is explicitly truncated at `To reproduce on a deployed build: create a survey wit`, and the full PR body is not available in the repository or reviewed Git refs. The omitted text could contain another `Fixes`, `Closes`, `Resolves`, or `Ref` pair. **Resolution** Provide the complete raw PR description, then scan all text for `Fixes|Closes|Resolves|Ref` next to `ENG-&lt;number&gt;` or `#&lt;number&gt;`. PASS only if `Fixes ENG-3946` is the sole pair. Comment `@the reviewer help` to get the list of available commands.
#9634
2026-10-20 20:37
cubic
inline · P2
docs/api-v3-reference/src/paths/api_v3_surveys_{surveyId}_blocks_order.yml:106docs
This says reordering blocks inside an existing loop is always refused, but a reorder is accepted when it preserves the existing cyclic edges (for example, two blocks with reciprocal explicit jumps). Describe the rejection as applying to orders that introduce new cyclic edges. ```suggestion block in front of an existing loop is fine; an order that adds a new cyclic edge is refused. ```
#9634
2026-10-20 20:37
cubic
inline · P2
docs/api-v3-reference/src/paths/api_v3_surveys_{surveyId}.yml:404docs
This overstates the detector’s guarantee: a conditional jump can form a cycle even when another path reaches an ending. Describe a path that can loop indefinitely rather than saying the respondent could never reach an ending. ```suggestion so a respondent may follow a path that loops indefinitely — is refused the ```
#9634
2026-10-20 20:37
cubic
review-body
—summary
**2 issues found** across 6 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9634
2026-10-20 20:39
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No blocking issues were found; the inspected change and passing focused checks support merging. Reviews (1) · Last reviewed commit: "fix(api-v3): refuse survey patches and b..." · Reviewed by the reviewer
#9643
2026-10-20 20:41
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The English survey messages now define `link_not_valid` with the text “This link is not valid.” The inactive survey heading and email verification error use this key instead of `this_looks_fishy`. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to 0cd84 The localization update is consistent across supported locales and invalid-link screens. Pre-merge checks: 6 · 1 Failed checks: 1 warning | Check name | Status | Explanation | Resolution | | :--------- | :----------------------------------------------------------------------------------------------: | :-----------------------------------------------------------
… the rest (3433 characters)------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title uses the required Conventional Commits format and accurately describes the change, but it ends with a period, which violates the requirement for no trailing period. | Remove the trailing period. For example: `fix(link-survey): say an invalid link is not valid instead of "This looks fishy"`. | Passed checks: 6 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Description check | | The description covers the user-visible change, affected screens, implementation locations, breaking-change status, migrations, testing coverage, open gaps, and risks. It also explains why no ticket i… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves “This PR contains breaking changes” unticked. The diff only replaces an internal translation key and its user-facing localized text in survey link components and locale files… | | Only The Ticket Line Carries A Magic Word | | The PR description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` pair next to an `ENG-<number>` or `#<number>`. No secondary closing reference exists outside the top ticket line. | | Coverage Accounts For The Whole Diff | | The description covers the full diff. The diff changes the shared translation key across all locale files and the rendered invalid-link headings in `SurveyInactive` and `VerifyEmail`. The Coverage tab… | Full details: Description check **Explanation** The description covers the user-visible change, affected screens, implementation locations, breaking-change status, migrations, testing coverage, open gaps, and risks. It also explains why no ticket is included. Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves “This PR contains breaking changes” unticked. The diff only replaces an internal translation key and its user-facing localized text in survey link components and locale files. It does not change an API shape, route, webhook, status code, default/configuration, database schema, or public SDK signature. Full details: Coverage Accounts For The Whole Diff **Explanation** The description covers the full diff. The diff changes the shared translation key across all locale files and the rendered invalid-link headings in `SurveyInactive` and `VerifyEmail`. The Coverage table records the new single-use-link heading with a manual outcome, translation coverage for all locales with `pnpm i18n`, and the other invalid-link screens with a traced outcome. The Open gaps section explains which screens were not captured live. A screenshot is attached for the rendered UI change. Comment `@the reviewer help` to get the list of available commands.
#9643
2026-10-20 20:41
cubic
review-body
—summary
**No issues found** across 18 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9643
2026-10-20 20:49
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Low impact]** Safe to merge. Both affected components render the intended heading, with surrounding text and controls unchanged. Reviews (1) · Last reviewed commit: "fix(link-survey): say an invalid link is..." · Reviewed by the reviewer
#9637
2026-10-20 20:50
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough `matchSurveyLanguage` now resolves a matched legacy code to its canonical stored sibling when one exists. It keeps the original code when no such sibling exists. The matcher applies this resolution to exact-code, alias, canonical, and same-base matches. Tests cover language-list ordering, default sentinels, browser resolution, unmatched explicit languages, and canonical requests. A compatibility comment was also updated. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to eafa1 The language-matching change is mergeable after normal checks; no specific behavior requiring a pre-merge fix was identified. Architect
… the rest (7222 characters)ure risk: 🔵 Low · up to eafa1 The change affects 3 systems. **Changed systems:** `packages/i18n-utils`, `apps/web`, `packages/surveys` **Architecture concerns** No architecture-level concerns identified. Review details **Systems and components** - _observed_ — packages/i18n-utils (library) was modified; 2 changed files map to changed impact. - _observed_ — apps/web (ui) was modified; 1 changed file maps to changed impact. - _observed_ — packages/surveys (library) was modified; 1 changed file maps to changed impact. **Before / after behavior** - _observed_ — Modified behavior in apps/web/app/api/v1/client/[workspaceId]/environment/lib/data.ts: Updated the legacy-language compatibility documentation to state that `matchSurveyLanguage` resolves duplicated legacy codes to their canonical sibling, ensuring new SDKs and the renderer use canonical content keys; the prior wording describing renderer canonicalization and canonical-entry ordering was removed. - _observed_ — Modified behavior in packages/i18n-utils/src/survey-language-match.test.ts: Added a test suite covering legacy duplicate language entries from the client environment endpoint, including canonical sibling selection, order-independent matching, legacy-code preservation when no canonical sibling exists, default-language sentinels, browser resolution, and SDK skip behavior for unmatched explicit languages. The accompanying comments document the endpoint’s real-language-plus-legacy-duplicate format. - _observed_ — Modified behavior in packages/i18n-utils/src/survey-language-match.ts: Added `preferCanonicalSibling`, which replaces a matched legacy code with a stored sibling whose code equals its normalized canonical form; unmatched or already canonical codes remain unchanged. - _observed_ — Modified behavior in packages/i18n-utils/src/survey-language-match.ts: Updated the matcher contract documentation to state that legacy duplicate matches resolve to the canonical stored code. Pre-merge checks: 6 · 1 Failed checks: 1 warning | Check name | Status | Explanation | Resolution | | :----------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Breaking Changes Match The Diff | | The breaking-changes checkbox is unticked, but the diff changes an emitted language value. In `packages/i18n-utils/src/survey-language-match.ts:116-123`, exact matches now pass through `preferCanonica… | Tick `- [x] This PR contains breaking changes` and replace `None` with a short explanation of the `de` → `de-DE` emitted language change and its affected SDK/renderer consumers. | Passed checks: 6 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses the allowed fix type and surveys scope, describes the language-resolution change, uses lower-case summary text, and appends a valid ENG-3961 referen… | | Description check | | The description includes the required ticket reference, change summary, review locations, breaking-change assessment, migration status, test command, coverage, open gaps, risks, and AI note. It clearl… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Only The Ticket Line Carries A Magic Word | | The only magic-word pair in the PR description is “Fixes ENG-3961” on the ticket line at the top. No other “Fixes”, “Closes”, “Resolves”, or “Ref” appears next to an ENG ticket ID or GitHub issue refe… | | Coverage Accounts For The Whole Diff | | The Coverage table is populated and maps the changed matcher behavior to named unit coverage for canonical duplicates, aliases and ordering, unmigrated surveys, SDK selection, and unmatched inputs. Ea… | Full details: Title check **Explanation** The title follows Conventional Commits format, uses the allowed fix type and surveys scope, describes the language-resolution change, uses lower-case summary text, and appends a valid ENG-3961 reference. Full details: Description check **Explanation** The description includes the required ticket reference, change summary, review locations, breaking-change assessment, migration status, test command, coverage, open gaps, risks, and AI note. It clearly explains the user-visible language-resolution fix. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The only magic-word pair in the PR description is “Fixes ENG-3961” on the ticket line at the top. No other “Fixes”, “Closes”, “Resolves”, or “Ref” appears next to an ENG ticket ID or GitHub issue reference. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table is populated and maps the changed matcher behavior to named unit coverage for canonical duplicates, aliases and ordering, unmigrated surveys, SDK selection, and unmatched inputs. Each row gives a verification method and observed outcome, and the shared Rerun line names the relevant specs. The description also covers renderer behavior with a manual result and attached before/after screenshots. The Open gaps subsection is present and records the missing full JS SDK staging flow with QA case ML-05. The only non-test source change outside the matcher is a compatibility comment, so no additional product behavior is unaccounted for. Full details: Breaking Changes Match The Diff **Explanation** The breaking-changes checkbox is unticked, but the diff changes an emitted language value. In `packages/i18n-utils/src/survey-language-match.ts:116-123`, exact matches now pass through `preferCanonicalSibling`; with both `de` and `de-DE` stored, `de` changes from the legacy result to `de-DE`. `packages/surveys/src/lib/i18n-utils.ts:23-25` returns that changed value to the renderer. This matches the check's emitted-value condition. **Resolution** Tick `- [x] This PR contains breaking changes` and replace `None` with a short explanation of the `de` → `de-DE` emitted language change and its affected SDK/renderer consumers. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 4 - Waived: $1.00 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9637
2026-10-20 20:53
cubic
review-body
—summary
**No issues found** across 4 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9637
2026-10-20 20:56
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** The scoped changes are safe to merge based on the focused execution checks; the full staging SDK flow remains unverified. Reviews (1) · Last reviewed commit: "fix(surveys): resolve bare language code..." · Reviewed by the reviewer
#9651
2026-10-20 20:57
coderabbit
issue-comment
—summary
### Walkthrough Patch validation now allows unchanged pre-existing validation issues and rejects newly introduced issues. Publishing a draft still requires a valid survey. Media validation compares current block URLs with the stored survey and validates media when those URLs change. The updated tests and API documentation describe these behaviors, including the handling of unreadable stored surveys. Priority: ➖ Normal Merge Risk: 🔵 Low · up to 6334e Surveys with existing invalid media can still reject an otherwise valid media edit. This is a bounded gap in the changed-media behavior; address it before merging or accept it explicitly. Security architecture risk: 🔵 Low · up to 6334e Ac
… the rest (8417 characters)cess controls remain in place, but the new media exemption also applies during publication. A draft whose only validation problem is unchanged media can therefore go live. Existing rendering protections limit the demonstrated security impact. **Retained concerns** - **Medium · architecture · inferred:** The ordinary-edit exemption leaks into draft publication. For an otherwise-valid draft containing parseable but invalid media, a status-only patch leaves the media fingerprint unchanged, so validation reports no media issue. The publication rejection is consequently skipped, and the write path also exempts that media. The base rejected this transition. This weakens the promised full-validation lifecycle gate, although respondent rendering retains independent URL protections. Security review details **Security Blast Radius** - _inferred_ — The demonstrated exposure concerns publication and persisted content of surveys the caller can already write. It requires an existing parseable media issue; this path does not establish the ability to introduce an arbitrary new invalid URL or cross a workspace boundary. **Security Findings and Attack Paths** - _inferred_ — An authorized writer can submit a status-only draft-publication patch while leaving invalid stored media unchanged. If no other validator reports an issue, preparation succeeds and persistence repeats the exemption. This is a publication-control regression, not a verified script-execution or unrestricted-framing vulnerability. **Trust Boundaries and Controls** - _observed_ — Independent respondent-side controls predate this PR: unsupported video URLs produce no embed, supported embeds use fixed provider origins, and image, frame and link sinks receive scheme-checked URLs. These controls materially limit the consequences of the publication-gate regression. **Resilience and Maintainability Implications** - _inferred_ — PATCH without updatedAt retains its pre-existing last-write-wins contract; block operations receive an implicit precondition. Because exemptions use the initially read survey and persistence rewrites merged blocks, an unconditioned concurrent PATCH can now restore grandfathered invalid media after another writer repairs it. This is a bounded consequence of the exemption combined with existing concurrency semantics, not a newly introduced general lost-update mechanism. **Hardening Proposals** - _proposed_ — Separate full draft-publication validation from delta-based edit acceptance so grandfathering cannot suppress media findings at the go-live boundary. If exemptions must describe the latest persisted version, bind their baseline to a write precondition rather than relying on an unconditioned snapshot. Pre-merge checks: 4 · 3 Failed checks: 3 warnings | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Description check | | The description includes the required sections, rationale, test coverage, risks, and open gaps. However, it incorrectly marks the change as non-breaking even though existing PATCH requests that return… | Check "This PR contains breaking changes" and document the changed HTTP behavior in the required breaking-changes table, including affected API consumers and any required action. | | Breaking Changes Match The Diff | | The checkbox is unticked, but the diff changes an existing PATCH response case. `apps/web/app/api/v3/surveys/prepare.ts:600-623` now accepts patches when stored validation issues are unchanged and ret… | Tick `- [x] This PR contains breaking changes` in the PR description and document the PATCH status change from 422 to 200 for stored surveys with untouched validation issues. | | Coverage Accounts For The Whole Diff | | The Coverage table is populated and the required Open gaps section and screenshots are present. However, it does not account for all changed behavior. The diff adds media-change validation: a changed … | Add Coverage rows with verification methods, named tests, and observed outcomes for (1) changed media URLs being validated and rejected in preparation and the write path, and (2) status changes on already-live surveys with untouched stored … | Passed checks: 4 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses a valid type and scope, describes the change, uses lower-case summary text, and appends the ENG-3975 reference without a trailing period. | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Only The Ticket Line Carries A Magic Word | | The PR description contains one magic-word pair: “Fixes ENG-3975” on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears next to an `ENG-<number>` or `#<number>` … | Full details: Only The Ticket Line Carries A Magic Word **Explanation** The PR description contains one magic-word pair: “Fixes ENG-3975” on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Description check **Explanation** The description includes the required sections, rationale, test coverage, risks, and open gaps. However, it incorrectly marks the change as non-breaking even though existing PATCH requests that returned HTTP 422 can now succeed, which the template explicitly classifies as a breaking change. **Resolution** Check "This PR contains breaking changes" and document the changed HTTP behavior in the required breaking-changes table, including affected API consumers and any required action. Full details: Breaking Changes Match The Diff **Explanation** The checkbox is unticked, but the diff changes an existing PATCH response case. `apps/web/app/api/v3/surveys/prepare.ts:600-623` now accepts patches when stored validation issues are unchanged and returns successful preparation. Before this change, the same validation failure returned `storedSurvey`, which `apps/web/app/api/v3/surveys/lib/operations.ts:834-847` maps to HTTP 422; successful writes return HTTP 200 at `operations.ts:1096-1110`. This changes the response status from 422 to 200 for existing requests. **Resolution** Tick `- [x] This PR contains breaking changes` in the PR description and document the PATCH status change from 422 to 200 for stored surveys with untouched validation issues. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table is populated and the required Open gaps section and screenshots are present. However, it does not account for all changed behavior. The diff adds media-change validation: a changed invalid media URL must be rejected, including the introduced-media helper behavior. Coverage only records that an unchanged stored bad URL permits a rename. The diff also tests acceptance of a status change on an already-live survey with stored issues, but no Coverage row or Open gap names that behavior. **Resolution** Add Coverage rows with verification methods, named tests, and observed outcomes for (1) changed media URLs being validated and rejected in preparation and the write path, and (2) status changes on already-live surveys with untouched stored issues being accepted. Alternatively, list either behavior under Open gaps with a reason. Comment `@the reviewer help` to get the list of available commands.
#9651
2026-10-20 21:03
coderabbit
inline · P2
apps/web/app/api/v3/surveys/validation.ts:193defect-functional
**🎯 Functional Correctness** | **🟡 Minor** | **⚡ Quick win** **Validate only changed media in patch mode.** `getV3SurveyIntroducedMediaInvalidParams` validates all current media after any URL changes. Because `checkForInvalidMediaInBlocks` fails on the first invalid URL, an unchanged invalid URL can reject a patch that changes another URL to a valid value. This affects both preparation and the write guard. Filter unchanged media in this helper. Keep the full validator unchanged for create and enforce modes. Suggested fix ```diff export function getV3SurveyIntroducedMediaInvalidParams( blocks: TV3SurveyDocument["blocks"], baselineBlocks: ReadonlyArray<Pick<TSurveyBlock, "element
… the rest (1698 characters)s">> ): InvalidParam[] { if (getMediaFingerprint(blocks) === getMediaFingerprint(baselineBlocks)) { return []; } - return getV3SurveyMediaInvalidParams(blocks); + const changedBlocks = blocks.map((block, blockIndex) => ({ + ...block, + elements: block.elements.map((element, elementIndex) => { + const baselineElement = baselineBlocks[blockIndex]?.elements[elementIndex]; + const { imageUrl, videoUrl, ...elementWithoutMedia } = element; + const changedElement = { + ...elementWithoutMedia, + ...(imageUrl !== baselineElement?.imageUrl && imageUrl ? { imageUrl } : {}), + ...(videoUrl !== baselineElement?.videoUrl && videoUrl ? { videoUrl } : {}), + }; + + if (!("choices" in element) || !Array.isArray(element.choices)) { + return changedElement; + } + + const baselineChoices = + baselineElement && "choices" in baselineElement && Array.isArray(baselineElement.choices) + ? baselineElement.choices + : []; + + return { + ...changedElement, + choices: element.choices.map((choice, choiceIndex) => { + if (!("imageUrl" in choice)) return choice; + const baselineChoice = baselineChoices[choiceIndex]; + const baselineImageUrl = + baselineChoice && "imageUrl" in baselineChoice ? baselineChoice.imageUrl : undefined; + + if (choice.imageUrl !== baselineImageUrl && choice.imageUrl) return choice; + const { imageUrl: _unchangedImageUrl, ...choiceWithoutImage } = choice; + return choiceWithoutImage; + }), + }; + }), + })); + + return getV3SurveyMediaInvalidParams(changedBlocks); } ```
#9651
2026-10-20 21:03
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9651
2026-10-20 21:06
cubic
inline · P2
apps/web/app/api/v3/surveys/patch.ts:298defect-functional
This rejects a valid media edit when another stored URL is already invalid, even though the edit introduces no issue at that URL. Validate the changed media values individually so unrelated legacy errors do not block the patch.
#9651
2026-10-20 21:06
cubic
inline · P2
docs/api-v3-reference/src/paths/api_v3_surveys_{surveyId}.yml:418docs
This overstates when publishing returns `stored_survey_invalid`: a patch that introduces a validation issue is rejected first as a request validation error. Clarify that this code applies when pre-existing issues remain unchanged. ```suggestion that passes; if only pre-existing issues remain, the response uses `stored_survey_invalid`. Newly introduced issues are reported as request validation errors. ```
#9651
2026-10-20 21:06
cubic
inline · P2
apps/web/app/api/v3/surveys/validation.ts:166defect-functional
This positional fingerprint treats block or element structure changes as media changes, even when every media URL is untouched. A block add, removal, or reorder can therefore make an unrelated patch fail on a legacy media error; compare URLs by stable element/choice identity instead of array position.
#9651
2026-10-20 21:06
cubic
review-body
—summary
**3 issues found** across 10 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9651
2026-10-20 21:08
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The change affects survey editing and patch validation across v3 API callers\. Not safe to merge until draft publishing rejects unchanged invalid media. The broken-reference type comparison is a separate non-blocking concern. Findings 1. **Invalid media can go live** 2. **Changed broken references pass** Reviews (1) · Last reviewed commit: "fix(api-v3): accept survey patches that ..." · Reviewed by the reviewer
#9651
2026-10-20 21:08
greptile
inline · P1
apps/web/app/api/v3/surveys/prepare.ts:613defect-functional
**Invalid media can go live** Publishing a draft with an unchanged invalid image URL succeeds. Introduced-only validation skips unchanged media, so when that is the draft's only issue, `validation.valid` is true and this publishing guard never runs. The write path skips the same media check. A status-only patch returned 200 and reached the survey update with `status: "inProgress"` and the invalid URL, although full validation rejected that exact document. Run full validation whenever a survey leaves `draft`, independently of the introduced-only result, while preserving lenient validation for ordinary edits. This publishing gate must be fixed before merging. Artifacts **Publishing respons
… the rest (1320 characters)es before the media-validation change** - Executed the parent preparation and PATCH service paths for three media fixtures, capturing full validation, responses, and write calls; unchanged invalid media is rejected with 422 and no update. **Publishing responses after the media-validation change** - Executed the head preparation and PATCH service paths for the same fixtures, capturing full validation, responses, and write calls; unchanged invalid media passes with 200 and reaches the update despite failing full validation. **Executed test for publishing drafts with invalid media** - Contains the exact executed fixtures, explicit mock boundaries, response capture, and assertions for both revisions; the test isolates candidate 0 through real validation and service code. **Runner for the before and after publishing tests** - Reconstructs parent modules under unique temporary filenames, runs both revisions, captures command output, and removes temporary application files; tracked implementation remains unchanged. **Source lines governing media validation and publishing** - Captured revision identifiers, the empty tracked diff, and numbered preparation, validation, write, and response source; the publishing guard is nested beneath introduced-validation failure. Ran code and verified through TREX
#9651
2026-10-20 21:08
greptile
inline · P2
apps/web/app/api/v3/surveys/prepare.ts:238defect-functional
**Changed broken references pass** The issue comparison misses changes to a broken reference's type. Changing a draft condition from `{type: "element", value: "missing"}` to `{type: "variable", value: "missing"}` creates a different dangling reference, but both errors have the same signature and point to the unchanged `.value` field. `getIntroducedIssues` therefore treats the changed operand as untouched. The PATCH returned 200 and reached the update with the broken variable reference. This is a non-blocking validation gap: callers can replace one invalid condition reference with another instead of receiving the expected rejection. Include `referenceType` in the signature or compare the wh
… the rest (1509 characters)ole operand, and cover this type change in a regression test. Artifacts **PATCH rejected with parent preparation** - Executed the operand-type change using parent preparation and the actual PATCH service/handler; HTTP 422 and zero writes show the changed broken reference was rejected. **PATCH accepted with head preparation** - Executed the identical operand-type change at head; HTTP 200 and the captured database update show the new variable dangling reference passed validation. **Executed reference-change test source** - Contains the executed fixture, real preparation and service calls, loopback HTTP request, assertions, and explicit mocks, making the validation scope reproducible. **Before and after test configuration** - Contains the executed Vitest configuration that substitutes only parent preparation for the baseline run, isolating the candidate1 change. **Parent preparation source executed for comparison** - Contains the exact parent prepare.ts loaded during the baseline run, preserving the implementation that rejected the request. **Executed before and after capture script** - Runs both test conditions with explicit environment values and records commands and exit codes, reproducing the paired evidence. **Revision hashes and relevant implementation lines** - Captured revision IDs, matching parent-source hashes, an empty tracked web diff, and numbered implementation excerpts, tying the observed acceptance to the candidate1 code. Ran code and verified through TREX
#9645
2026-10-20 21:10
coderabbit
issue-comment
—summary
### Walkthrough The Docker installer now refreshes bundled Cube model files during updates and through a new `refresh-cube` command. It selects a model release from supported Formbricks image references, stages both files, and backs up existing files before replacement. Failure handling retains or restores existing files, and refresh failures do not block updates. Tests cover refresh success, failure, retry, and unsupported image references. The documentation describes the Docker refresh paths and the Helm model update behavior. Priority: ➖ Normal **Severity of issue fixed:** Medium Merge Risk: 🔵 Low · up to c496f Automatic and on-demand Cube model refreshes for Docker installs look so
… the rest (5947 characters)und and fail safely. One documentation gap remains in the manual upgrade steps. Running them a second time replaces the saved copy of the previous model, so operators who rerun the steps lose that rollback copy. This is a small documentation fix and does not block merging. Security architecture risk: 🔵 Low · up to c496f The normal update path stages both files and refreshes them while services are stopped. However, failed restoration can leave configuration files from different releases and still restart the stack. Impact is bounded to the updated deployment; no authorization bypass was demonstrated. **Retained concerns** - **Medium · reliability · observed:** If schema replacement fails after cube.js replacement and restoring cube.js also fails, the updater deliberately continues startup with files from different releases. The test explicitly expects this terminal state. This violates the paired configuration rollback guarantee and can strand the deployment with incompatible analytics configuration. These files include tenant-query enforcement, although no authorization bypass from the mismatch was demonstrated. Security review details **Security Blast Radius** - _inferred_ — Upstream-controlled JavaScript becomes executable configuration in the refreshed deployment's Cube service, which has database credentials and the Cube API secret. A malicious configuration could therefore affect all tenants accessible through that database account, rather than only the operator's current tenant. Trusting upstream configuration predates this PR; the new lifecycle repeats that import during updates. **Trust Boundaries and Controls** - _observed_ — Repository selection is fixed and release-reference input is restricted; the download path does not accept an arbitrary URL from analytics requests. HTTPS retrieval is used, but the refreshed files are not checksum- or signature-bound to the pulled image digest. Read-only container mounts do not authenticate host-side replacements. **Resilience and Maintainability Implications** - _inferred_ — Keeping prior copies and staging outside the loaded schema directory improves ordinary recovery. However, continuing startup after failed restoration sacrifices configuration coherence to avoid leaving the entire stack down. Source evidence establishes that trade-off, not a resulting tenant-isolation bypass. **Hardening Proposals** - _proposed_ — Serialize refreshes and retain a recoverable transaction or generation until both files are validated and Cube is recreated. If restoration fails, isolate Cube rather than starting it with a known mixed pair. An immutable release manifest could additionally bind both files to the deployed image. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses an allowed type and scope, describes the Cube model refresh, uses lower-case summary text, and includes a valid Linear ticket reference. | | Description check | | The description covers the required ticket reference, change summary, review locations, breaking changes, migrations, test coverage, open gaps, risks, and AI metadata. It is consistent with the pull r… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR declares no breaking changes with an unticked checkbox. The reviewed diff changes only Docker updater logic, Docker tests, and documentation. It adds Cube model refresh behavior and the `refres… | | Only The Ticket Line Carries A Magic Word | | The description contains one magic-word pair, “Fixes ENG-3951,” at the top ticket line. No other “Fixes”, “Closes”, “Resolves”, or “Ref” appears next to an ENG- or # reference. | | Coverage Accounts For The Whole Diff | | The description contains a non-empty Coverage table and an explicit Open gaps section. The rows cover the changed update flow, release and digest selection, staged downloads, backup/replacement failur… | Full details: Description check **Explanation** The description covers the required ticket reference, change summary, review locations, breaking changes, migrations, test coverage, open gaps, risks, and AI metadata. It is consistent with the pull request changes. Full details: Breaking Changes Match The Diff **Explanation** The PR declares no breaking changes with an unticked checkbox. The reviewed diff changes only Docker updater logic, Docker tests, and documentation. It adds Cube model refresh behavior and the `refresh-cube` command, but it does not change an API shape, route, webhook payload, HTTP status, environment/config key, database schema, or public SDK signature covered by this check. Full details: Coverage Accounts For The Whole Diff **Explanation** The description contains a non-empty Coverage table and an explicit Open gaps section. The rows cover the changed update flow, release and digest selection, staged downloads, backup/replacement failure handling, retry behavior, installations without bundled Cube files, and the manual Docker and upgrade paths. Each row states a verification method and observed outcome. The unit rows name the Cube refresh suite through the Rerun line; the mutation rows include `docker/formbricks.sh` line references. The diff changes shell behavior and documentation, not product UI, so no screenshot or screencast is required. Comment `@the reviewer help` to get the list of available commands.
#9645
2026-10-20 21:14
coderabbit
inline · P2
docs/self-hosting/setup/docker.mdx:435defect-data
**🗄️ Data Integrity & Integration** | **🟡 Minor** | **⚡ Quick win** **Skip the backup when the files are already current, so a second run keeps the rollback copy.** The manual snippet always copies the live files into `cube/previous/` before it replaces them. The updater does not do this. `refresh_bundled_cube_files` in `docker/formbricks.sh` compares the files with `cmp -s` and stops before the backup step when nothing changed. The operator can run this step a second time. For example, line 443-445 tells them to stop when `formbricks-migrate` fails and then retry the update. On that second run, `cube/cube.js` and `cube/schema/FeedbackRecords.js` already hold the release files. The `cp
… the rest (1341 characters)-p` lines then overwrite `cube/previous/` with the release files, and the old model is gone. The old model is the copy that this section and the migration guide tell the operator to keep. To fix this, add the same comparison before the backup. This also removes the leftover `.download` files on every path. 🛡️ Proposed fix ```diff if [ -f cube/schema/FeedbackRecords.js ]; then base=https://raw.githubusercontent.com/formbricks/formbricks/stable/docker curl -fsSL -o cube/cube.js.download "$base/cube/cube.js" && curl -fsSL -o cube/FeedbackRecords.js.download "$base/cube/schema/FeedbackRecords.js" && + if cmp -s cube/cube.js.download cube/cube.js && + cmp -s cube/FeedbackRecords.js.download cube/schema/FeedbackRecords.js; then + rm -f cube/cube.js.download cube/FeedbackRecords.js.download + else mkdir -p cube/previous/schema && cp -p cube/cube.js cube/previous/cube.js && cp -p cube/schema/FeedbackRecords.js cube/previous/schema/FeedbackRecords.js && mv cube/cube.js.download cube/cube.js && { # Never leave the new cube.js paired with the old model. mv cube/FeedbackRecords.js.download cube/schema/FeedbackRecords.js || { cp -p cube/previous/cube.js cube/cube.js; false; } } + fi fi ```
#9645
2026-10-20 21:14
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9645
2026-10-20 21:17
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The change reaches Docker\-based self\-hosted upgrades and Cube analytics\. No blocking findings remain; the release-consistency concern is non-blocking. Findings 1. **Cube files can disagree** Reviews (1) · Last reviewed commit: "fix(docker): refresh the bundled Cube mo..." · Reviewed by the reviewer
#9645
2026-10-20 21:18
greptile
inline · P2
docker/formbricks.sh:1371defect-concurrency
**Cube files can disagree** Both downloads use the mutable `stable` ref for a `latest` installation. If a release moves `stable` between requests, both downloads can succeed but come from different commits. Staging and rollback will not catch this mixed pair, which may not work together. This is a non-blocking consistency concern: resolve `stable` to one commit before downloading either file, then use that commit for both URLs. Artifacts **Evidence from the check** - The complete authored harness remains readable at /home/user/repo/trex-artifacts/cube-files-disagree-harness.mjs; remote upload is blocked by the absence of an upload mechanism. **Command output from the check** - The exis
… the rest (762 characters)ting command capture records a successful A/A installation with a fixed mock ref; it remains accessible locally but is not verified uploaded. **Command output from the check** - The existing command capture records a successful A/B installation when the mock ref moves between requests; it remains accessible locally but is not verified uploaded. **Command output from the check** - The existing capture contains numbered production and fixture source excerpts plus the executed harness hash; it preserves traceability locally without claiming remote upload. **Command output from the check** - The prior narrow test-suite capture records nine passing Cube refresh tests; it remains locally accessible and was not rerun. Ran code and verified through TREX
#9645
2026-10-20 21:19
cubic
inline · P2
docs/self-hosting/setup/docker.mdx:415defect-functional
This replaces the bind-mounted Cube files before `docker compose down`, leaving the running service exposed to a mixed-version pair during replacement. Move this refresh after stopping the stack, as the updater does.
#9645
2026-10-20 21:19
cubic
inline · P2
docs/self-hosting/setup/one-click.mdx:358docs
The rollback guarantee is not unconditional: if replacing a file and restoring its backup both fail, the updater leaves mixed-release files and prints manual recovery steps, not `refresh-cube`. Document this recovery exception so operators do not rely on both prior files remaining intact. ```suggestion downloaded or backed up, both stay as they were. If replacing a file and restoring its backup both fail, the updater prints manual recovery steps instead. Otherwise, the update continues and prints `./formbricks.sh refresh-cube` to retry. An image pinned to anything else, such as a digest, leaves ```
#9645
2026-10-20 21:19
cubic
inline · P2
docs/self-hosting/advanced/migration.mdx:482docs
The CSAT preset does not query `npsCount`; it uses `csatCount` and `valueBand`, so this error does not describe its failure. Refer to a missing Cube member generically or name `valueBand`.
#9645
2026-10-20 21:19
cubic
review-body
—summary
**5 issues found** across 5 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9645
2026-10-20 21:19
cubic
inline · P2
docker/formbricks.sh:1371defect-concurrency
Resolve `stable` to one commit before downloading either file, then use that immutable commit for both URLs; otherwise a branch update between requests can install a mixed-release model.
#9645
2026-10-20 21:19
cubic
inline · P2
docs/self-hosting/setup/docker.mdx:426defect-data
Skip the backup when both downloaded files already match the live model; rerunning this step otherwise overwrites `cube/previous/` with the release files and loses the original model.
#9640
2026-10-20 21:21
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The web client and surveys UMD entry point configure Zod with `jitless: true` before related initialization. Package metadata marks the configuration module as side-effectful, and tests cover both entry points. The Playwright test server now serves sample HTML with a nonce-based CSP. The browser test checks for CSP violations during the SDK journey, application page, and link survey. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to fbde0 The CSP checks retain reports through each tested journey. No issue identified here requires resolution before merge. Security architecture risk: 🔵 Low · up to fbde0 The i
… the rest (5985 characters)nspected changes support strict CSP without adding eval permissions or expanding application privileges. The main dependency is preserving initialization order in shipped browser bundles; that behavior was supported by source inspection but not verified against generated bundles. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The directly affected production scope is browser execution of the surveys UMD bundle on customer pages and browser startup of the web application. The inspected initializer changes parser execution configuration, not tenant identity, credentials, or server-side authorization. **Trust Boundaries and Controls** - _observed_ — The added CSP collectors observe document violation events and browser console reports for test assertions. They do not change production headers or introduce a production data-collection sink. Host-page checks occur before navigation to subsequent application and survey documents. Pre-merge checks: 6 · 1 Failed checks: 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Only The Ticket Line Carries A Magic Word | | The visible description contains the ticket line `Fixes ENG-3969` at the top and no other visible magic-word pair. The description is explicitly truncated after the AI attribution, so the complete raw… | Provide the complete pull request description, including the omitted text, and rescan the raw body. PASS only if no additional magic-word pair appears outside the top ticket line. | Passed checks: 6 passed | Check name | Status | Explanation | | :---------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title uses the required Conventional Commits format, describes the CSP and Zod changes, uses a lowercase summary without a trailing period, and includes the ENG-3969 reference. | | Description check | | The description includes the required ticket reference, change summary, review links, breaking-change status, migration details, test commands, coverage, open gaps, risks, and AI metadata. The content… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR marks Breaking changes as unticked. The diff adds CSP/Zod initialization, tests, coverage configuration, and a UMD wrapper. `packages/surveys/src/umd.ts` uses `export * from "./index"`, and the… | | Coverage Accounts For The Whole Diff | | The description has a non-empty Coverage table and an Open gaps subsection. It covers the SDK journey under the nonce CSP, the app page and link survey under the app CSP, the nonce and style regressio… | Full details: Description check **Explanation** The description includes the required ticket reference, change summary, review links, breaking-change status, migration details, test commands, coverage, open gaps, risks, and AI metadata. The content matches the pull request changes. Full details: Breaking Changes Match The Diff **Explanation** The PR marks Breaking changes as unticked. The diff adds CSP/Zod initialization, tests, coverage configuration, and a UMD wrapper. `packages/surveys/src/umd.ts` uses `export * from "./index"`, and the existing public exports and package entry paths remain unchanged. The diff does not change request or response shapes, routes, status codes, environment variables, database schema, or public SDK signatures. Full details: Coverage Accounts For The Whole Diff **Explanation** The description has a non-empty Coverage table and an Open gaps subsection. It covers the SDK journey under the nonce CSP, the app page and link survey under the app CSP, the nonce and style regression checks, UMD initialization order, and browser instrumentation. Each row provides a method and outcome. The Rerun line names `apps/web/playwright/js.spec.ts`, `src/umd.test.ts`, and `instrumentation-client.test.ts`. Both mutation rows identify the edited file and lines. The diff does not introduce an unaccounted product behavior, and the description includes screenshots. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The visible description contains the ticket line `Fixes ENG-3969` at the top and no other visible magic-word pair. The description is explicitly truncated after the AI attribution, so the complete raw body cannot be scanned for additional `Fixes`, `Closes`, `Resolves`, or `Ref` pairs. **Resolution** Provide the complete pull request description, including the omitted text, and rescan the raw body. PASS only if no additional magic-word pair appears outside the top ticket line. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 9 - Waived: $2.25 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9640
2026-10-20 21:28
cubic
inline · P2
apps/web/playwright/js.spec.ts:309tests
This assertion runs with `NODE_ENV=test`, where `next.config.mjs` adds `'unsafe-eval'`, so Zod's eval probe is permitted and cannot trigger the violation this test is meant to catch. Run this check against the production CSP or provide a test CSP without `'unsafe-eval'`.
#9640
2026-10-20 21:28
cubic
inline · P2
packages/surveys/package.json:14defect-functional
This allowlist cannot protect the emitted UMD module: the package exposes `dist/*` and ships no `src/*` files, so bundlers may drop `dist/index.umd.cjs` on side-effect-only imports. Include the emitted entry in the allowlist.
#9640
2026-10-20 21:28
cubic
review-body
—summary
**2 issues found** across 9 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9640
2026-10-20 21:31
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No blocking correctness or security issues were found in the changed behavior; the changes are safe to merge within the stated coverage. Reviews (1) · Last reviewed commit: "fix: run the JS SDK journey under a cust..." · Reviewed by the reviewer
#9656
2026-10-20 21:33
coderabbit
issue-comment
—summary
### Walkthrough Online upgrade blockers can expose a bounded error name and code without including the original error message. The CLI and runtime use shared blocker conversion. Runtime migration execution resolves the database migration script relative to the CLI bundle asset root. Tests cover failure metadata and the runtime image layout. Documentation describes failed-attempt status and how to inspect coordinator Job logs. Priority: ➖ Normal Merge Risk: 🔵 Low · up to 15f29 The change fixes online upgrade script resolution and adds bounded failure details. The migration script's presence in the built image is not verified by an artifact-level check, so the owner should be aware of tha
… the rest (6047 characters)t gap. No concrete failure is established. Security architecture risk: 🔵 Low · up to 15f29 The new diagnostics exclude error messages, and migration execution retains the existing command, privilege, and phase controls. No introduced security failure was established. Interruption and recovery of the restored migration path still need validation in the deployed image. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _observed_ — Migration authority is scoped to the configured database, not an individual tenant request. The runner uses MIGRATE_DATABASE_URL when supplied and passes that same connection to any Prisma deployment subprocess. Restoring execution therefore affects database-wide upgrade operations without adding credentials or a new privilege grant. **Security Findings and Attack Paths** - _inferred_ — No introduced attack path was established in the inspected operator flow. Migration execution uses a bundle-derived filename and argument-array invocation, while unexpected error messages are excluded from the returned JSON. This conclusion is limited to the inspected paths, not a complete security assessment. **Trust Boundaries and Controls** - _observed_ — The coordinator binds durable state to the target image, rechecks prerequisites on retries, and requires consumer verification before finalization and activation. These pre-existing controls remain in place around the restored migration execution. **Resilience and Maintainability Implications** - _inferred_ — The runner's nested Prisma invocation lacks cancellation propagation, but this alone does not establish an online-upgrade defect: the parent first applies the same approved schema set, and the runner skips deployment for fully applied batches. Online index inspection and repair also have their own session lock. End-to-end termination of database work during child interruption remains unverified. **Hardening Proposals** - _proposed_ — Validate lock loss and timeout while the actual bundled runner is performing database work, confirming termination before retry ownership is transferred and successful recovery without premature activation. This would resolve the remaining failure-containment uncertainty; it is not an observed vulnerability. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses the valid `fix(upgrade)` type and scope, describes the migration-path change, uses lower-case text without a trailing period, and includes the ENG-3… | | Description check | | The description is complete and matches the required template. It explains the change, links to risky code, addresses breaking changes and migrations, lists test coverage, records open gaps and risks,… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The Breaking changes checkbox is unticked. The diff changes only online-upgrade internals, tests, runtime script resolution, and documentation. `OnlineStatus` adds optional `cause`, which is explicitl… | | Only The Ticket Line Carries A Magic Word | | PASS. The only magic-word pair in the raw description is the allowed top ticket line, “Fixes ENG-3949”. No other occurrence of “Fixes”, “Closes”, “Resolves”, or “Ref” appears next to an `ENG-<number>`… | | Coverage Accounts For The Whole Diff | | The description has a non-empty Coverage table and an Open gaps section. Coverage addresses the changed migration path, image layout, sanitized error causes, configuration failures, packaged CLI outpu… | Full details: Title check **Explanation** The title follows Conventional Commits format, uses the valid `fix(upgrade)` type and scope, describes the migration-path change, uses lower-case text without a trailing period, and includes the ENG-3949 reference. Full details: Description check **Explanation** The description is complete and matches the required template. It explains the change, links to risky code, addresses breaking changes and migrations, lists test coverage, records open gaps and risks, and identifies the AI model used. Full details: Breaking Changes Match The Diff **Explanation** The Breaking changes checkbox is unticked. The diff changes only online-upgrade internals, tests, runtime script resolution, and documentation. `OnlineStatus` adds optional `cause`, which is explicitly additive. The diff adds no required request/response field, endpoint or status change, config key change, new manual database migration, destructive schema change, or public SDK signature change. Full details: Only The Ticket Line Carries A Magic Word **Explanation** PASS. The only magic-word pair in the raw description is the allowed top ticket line, “Fixes ENG-3949”. No other occurrence of “Fixes”, “Closes”, “Resolves”, or “Ref” appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Coverage Accounts For The Whole Diff **Explanation** The description has a non-empty Coverage table and an Open gaps section. Coverage addresses the changed migration path, image layout, sanitized error causes, configuration failures, packaged CLI output, and an end-to-end upgrade. Unit rows use the rerun command `pnpm --filter @formbricks/web exec vitest run lib/upgrade/online`; the mutation row names `coordinator.ts:38` and `:40`. Each row states a method and outcome. The diff does not change rendered UI. Comment `@the reviewer help` to get the list of available commands.
#9656
2026-10-20 21:35
coderabbit
inline · P1
apps/web/lib/upgrade/online/runtime-execution.test.ts:237tests
**📐 Maintainability & Code Quality** | **🟠 Major** | **🏗️ Heavy lift** **Verify the migration script in the built image.** This test reads Dockerfile instructions, but a matching `COPY` of `packages/database/dist` does not establish that `scripts/apply-migrations.js` exists in the image. Add an image smoke check for the resolved script path. Keep the path assertion that checks what `runOnlineCommand` executes. As per path instructions: “Assert on inputs and outputs. Flag tests that lock implementation details, markup, or snapshots.” _Source: Path instructions_
#9656
2026-10-20 21:35
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9656
2026-10-20 21:41
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge within the stated scope. The migration-script path and additive error-reporting behavior were exercised successfully. Reviews (1) · Last reviewed commit: "fix(upgrade): run the online migration s..." · Reviewed by the reviewer
#9656
2026-10-20 21:41
cubic
inline · P2
apps/web/lib/upgrade/online/coordinator.test.ts:155tests
This assertion allows extra fields in `cause`, so a message that omits `password` can still pass despite the test claiming messages are never reported. Assert the cause with `toEqual` to enforce the `{ name, code }` shape. ```suggestion expect(reported).toMatchObject({ blockerCode: "online_upgrade_failed", }); expect(reported.cause).toEqual({ name: "Error", code: null }); ```
#9656
2026-10-20 21:41
cubic
inline · P2
apps/web/lib/upgrade/online/runtime-execution.test.ts:247tests
Add an image smoke check that verifies the resolved `apply-migrations.js` path exists; this `COPY` assertion only proves the directory is copied, not that the executed script is present.
#9656
2026-10-20 21:41
cubic
review-body
—summary
**2 issues found** across 7 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9642
2026-10-20 21:43
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The database Vite build now emits the migration-phase script as a bundle entry. The Hub migration-wait init container uses maintenance-phase selection to determine which schema and data migration markers to check. If phase metadata is unavailable, it expects every migration marker. A new integration test covers deferred migrations, missing markers, compatibility fallback, and helper-load failures. Helm chart validation now runs this test. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to 096fd The Hub wait change is mergeable after normal checks; no actionable startup-readiness risk was established. Security
… the rest (5135 characters)architecture risk: 🔵 Low · up to 096fd The startup check now matches the migrations the Job actually runs while continuing to require successful completion of applicable migrations. No introduced security bypass was identified. Remaining uncertainty concerns production-image integration and compatibility, which were assessed from source and fixture coverage rather than live deployment execution. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The affected outcome is Hub startup for deployments using this gate and their configured database. Phase-selection inputs come from image-shipped migration identifiers and a constant maintenance phase, rather than tenant request parameters. Altering those inputs requires control of the deployed image or its filesystem, not merely application-level tenant access. **Trust Boundaries and Controls** - _observed_ — The startup gate retains the existing service-account-authenticated Kubernetes Job lookup and database success-marker checks. Phase filtering changes which markers are required, not the Kubernetes identity, API destination, or acceptance of failed and incomplete markers. - _observed_ — The routed shell entrypoint is a fixture-based chart-validation test, not a new network-facing application endpoint. Its execution substitutes Kubernetes service-account reads and API responses, uses fixture database modules, and runs in the existing workflow with contents-read permission and checkout credential persistence disabled. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits with the valid `fix(helm)` prefix, a descriptive lower-case summary, no trailing period, and an appended ENG-3948 reference. | | Description check | | The description includes the required ticket reference, change summary, review locations, breaking-change status, migration and environment notes, test coverage, open gaps, risks, and agent metadata. … | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description has an unticked Breaking changes checkbox. The diff only changes the internal Helm Hub migration-wait logic, adds its validation test, adds a workflow test invocation, and adds an i… | | Only The Ticket Line Carries A Magic Word | | The only magic-word and issue-id pair is the ticket line `Fixes ENG-3948` at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-<number>` or `#<number>` in the descripti… | | Coverage Accounts For The Whole Diff | | PASS. The Coverage table is populated and the Rerun command names `charts/formbricks/tests/hub-migration-wait.sh` for the unit rows. It covers deferred data and schema markers, required applied migrat… | Full details: Description check **Explanation** The description includes the required ticket reference, change summary, review locations, breaking-change status, migration and environment notes, test coverage, open gaps, risks, and agent metadata. It is complete and relevant to the changes. Full details: Breaking Changes Match The Diff **Explanation** The PR description has an unticked Breaking changes checkbox. The diff only changes the internal Helm Hub migration-wait logic, adds its validation test, adds a workflow test invocation, and adds an internal database build entry. It does not change an API shape, route, webhook/status, environment variable or config default, database schema migration, or the public SDK surface. The workspace-package bundle entry is outside the defined breaking-change scope. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The only magic-word and issue-id pair is the ticket line `Fixes ENG-3948` at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` in the description. Full details: Coverage Accounts For The Whole Diff **Explanation** PASS. The Coverage table is populated and the Rerun command names `charts/formbricks/tests/hub-migration-wait.sh` for the unit rows. It covers deferred data and schema markers, required applied migrations, unnamed data migration matching, missing-helper fallback, and fatal helper-load errors. Mutation rows name the edited `hub-deployment.yaml` lines. Manual rows cover helper emission and upgrade recovery. The `Open gaps` subsection is present and records the incomplete full-image and older-image checks. No rendered UI changes are present. Comment `@the reviewer help` to get the list of available commands.
#9642
2026-10-20 21:50
cubic
review-body
—summary
**No issues found** across 4 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9642
2026-10-20 21:50
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge based on the rendered startup checks and built-helper verification; no blocking issues were found. Reviews (1) · Last reviewed commit: "fix(helm): skip deferred migrations in t..." · Reviewed by the reviewer
#9650
2026-10-20 21:53
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The preparation script now includes invalid configuration variable names in blocked responses. It extracts and deduplicates uppercase variable names from environment-validator output. Tests check the response and confirm that detailed validation messages are not exposed in stdout. The maintenance documentation describes required environment settings, the WEBAPP_URL failure condition, and the optional variables array. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to f362b The variable reporting and maintenance guidance have no identified issue requiring a change before merge. Security architecture risk: 🔵 Low
… the rest (6872 characters)· up to f362b The changes preserve the maintenance checks and report configuration variable names without forwarding detailed validator output. No introduced security weakness was identified. Preparation still depends on operators supplying matching deployment settings and stopping writers before database changes. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _observed_ — The sensitive operation is the existing service-credential migration against the configured database. Its OAuth backfill covers clients in that database rather than a single requesting tenant, including resource links and empty token or consent resource fields. The PR does not change those writes or add a remote caller. **Trust Boundaries and Controls** - _observed_ — The new diagnostic path retains only uppercase identifier-shaped tokens preceding a colon on matching validator lines; it does not forward the remaining message or raw output. Other command output remains suppressed. Existing backup and writer-stop acknowledgements, argument checks and fully consistent AuthZed configuration gates remain before migration execution. **Resilience and Maintainability Implications** - _observed_ — The existing runner executes data migrations transactionally and skips applied migrations. OAuth inserts use conflict handling, while token and consent updates affect empty resource fields, supporting convergent retries with the same configuration. Writer shutdown remains an operator assertion, and retries of failed migrations do not establish an exclusive retry lease; these are pre-existing operational limitations, not introduced concerns. Pre-merge checks: 5 · 2 Failed checks: 2 warnings | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title accurately describes the changes and uses a valid Conventional Commits prefix and ticket reference. However, the summary is not fully lowercase because it contains "Job". | Use a lowercase summary, for example: `fix(authzed): document the prepare job's full environment and name invalid variables [ENG-3953]`. | | Coverage Accounts For The Whole Diff | | The Coverage table is not compliant with the unit-row naming rule. It has three rows marked `unit`, but none names a test or spec: the environment-validation row, the AuthZed configuration row, and th… | Name a test or spec in each `unit` row, such as the relevant `maintenance-upgrade.test.ts` test title, while keeping the existing Rerun command. The rows already state verification outcomes, and the `Open gaps` subsection is present. | Passed checks: 5 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Description check | | The description includes all required sections, explains the behavior change, identifies affected documentation and code, records testing coverage, and lists known gaps and risks. The appended generat… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves “This PR contains breaking changes” unticked. The diff adds only an optional `variables` field to the blocked preparation JSON; the check explicitly treats a new optional res… | | Only The Ticket Line Carries A Magic Word | | The description contains one magic-word pair: “Fixes ENG-3953” on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-<number>` or `#<number>` referenc… | Full details: Description check **Explanation** The description includes all required sections, explains the behavior change, identifies affected documentation and code, records testing coverage, and lists known gaps and risks. The appended generated summary is redundant but does not make the description incomplete. Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves “This PR contains breaking changes” unticked. The diff adds only an optional `variables` field to the blocked preparation JSON; the check explicitly treats a new optional response field as non-breaking. It does not rename or remove an endpoint, environment variable, config key, database field, or SDK signature. The six documented Job variables have inert defaults, and the existing validator already ran before this change. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The description contains one magic-word pair: “Fixes ENG-3953” on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table is not compliant with the unit-row naming rule. It has three rows marked `unit`, but none names a test or spec: the environment-validation row, the AuthZed configuration row, and the other-failing-steps row. The Rerun line (`pnpm --filter @formbricks/docker test`) is a bare suite command and does not name a test. The diff confirms these behaviors are tested in `docker/__tests__/maintenance-upgrade.test.ts`, but the description does not identify those tests in the table. **Resolution** Name a test or spec in each `unit` row, such as the relevant `maintenance-upgrade.test.ts` test title, while keeping the existing Rerun command. The rows already state verification outcomes, and the `Open gaps` subsection is present. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 3 - Waived: $0.75 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9650
2026-10-20 21:53
cubic
inline · P2
docs/self-hosting/advanced/v6-maintenance-upgrade.mdx:195docs
`WEBAPP_URL` is not required for every preparation: the migration returns without reading it when the database has no OAuth clients. Restrict this requirement to databases with existing MCP OAuth clients.
#9650
2026-10-20 21:53
cubic
review-body
—summary
**1 issue found** across 3 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9650
2026-10-20 21:59
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The changes affect the bounded v5\-to\-v6 maintenance preparation workflow\. The remaining diagnostic gap is non-blocking: invalid configuration still stops preparation, so this finding does not make the change unsafe to merge. Findings 1. **List errors lose their names** Reviews (1) · Last reviewed commit: "fix(authzed): document the prepare Job's..." · Reviewed by the reviewer
#9650
2026-10-20 21:59
greptile
inline · P2
apps/web/scripts/docker/formbricks-authzed-prepare:72defect-functional
**List errors lose their names** The matcher skips errors inside list settings. An invalid entry in `MCP_DCR_ALLOWED_REDIRECT_URIS` produces ` - MCP_DCR_ALLOWED_REDIRECT_URIS.0: ...`, but the matcher requires a colon immediately after the variable name. Preparation therefore returns a bare error, or an incomplete `variables` array when other settings also fail. This is non-blocking: invalid configuration is still rejected, but operators must inspect the validator separately to identify the list setting to fix. Accept an optional numeric path suffix while reporting only the top-level variable name, and add coverage for list-only and mixed list/scalar errors. Artifacts **Executable valida
… the rest (1035 characters)tor and wrapper reproduction source** - The executed harness loads each revision’s real schema, formatter, and wrapper and asserts five scenarios with downstream executable stubs, making the narrow reproduction repeatable without services. **Base behavior for indexed and scalar validation failures** - Executed five scenarios against the supplied base commit and captured validator output, wrapper responses, exit codes, and call traces, showing that base returns bare configuration errors. **Head behavior showing omitted redirect-list diagnostics** - Executed the same five scenarios against the supplied head commit and captured real indexed errors flowing into the wrapper, confirming that scalar names are reported while the redirect-list variable is omitted. **Existing maintenance wrapper tests at head** - Ran the existing maintenance-upgrade test file with repository Vitest and captured all 20 tests passing, showing that existing coverage does not catch the indexed-path omission. Ran code and verified through TREX
#9662
2026-10-20 22:02
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The change adds shared helpers to create relationship keys and compare relationship sets without depending on order. Relationship diffing now uses the shared key helper. Projection verification uses the set matcher to detect drift, with tests for exact matches, missing edges, and unexpected edges. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to 02f1f This change fixes online upgrade finalization for instances with surveys by comparing expected and observed survey edges as sets. Drift is still repaired and still blocks activation. No actionable merge-blocking risk remains. Security architecture risk: ⚪ Minim
… the rest (4907 characters)al · up to 02f1f The fix allows upgrades to proceed when survey permissions already match their expected state. Missing or unexpected permissions still trigger repair and block that activation attempt. No material security risk introduced or worsened by this change was identified. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The affected gate has installation-wide rollout significance because finalization scans surveys across workspaces before enabling survey projection readiness. Individual comparisons and repairs remain scoped to survey resource IDs; the PR introduces no new authority to select another tenant or modify ownership. **Trust Boundaries and Controls** - _observed_ — PostgreSQL remains authoritative for survey ownership and visibility; the authorization graph remains a derived projection. The changed comparison does not redefine expected permissions. Reconciliation retains per-survey advisory locking, fact rereads, and version-guarded acknowledgement. **Resilience and Maintainability Implications** - _observed_ — Relationship-read failures, inconsistent page revisions, and observation-limit violations propagate rather than becoming a successful empty observation. Stable drift blocks activation after a repair attempt regardless of its returned status. Existing outbox dead-letter and overdue-revocation gates remain enforced. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses a valid type and scope, describes the set-comparison fix, uses lowercase summary text, and includes the ENG-3950 reference. | | Description check | | The description includes the required ticket reference, change summary, code locations, breaking-change status, migration details, test coverage, open gaps, risks, and agent note. It clearly explains … | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves the breaking-change checkbox unticked and states `None`. The reviewed diff changes only internal survey relationship comparison, reconciliation, projection logic, and tests i… | | Only The Ticket Line Carries A Magic Word | | The description contains one magic-word pair: `Fixes ENG-3950` on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-<number>` or `#<number>` referenc… | | Coverage Accounts For The Whole Diff | | The description has a non-empty Coverage table and an explicit Open gaps section with `none`. It covers the changed behaviors: exact expected edges pass without repair, missing or unexpected edges tri… | Full details: Description check **Explanation** The description includes the required ticket reference, change summary, code locations, breaking-change status, migration details, test coverage, open gaps, risks, and agent note. It clearly explains the online finalize fix and its verification. Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves the breaking-change checkbox unticked and states `None`. The reviewed diff changes only internal survey relationship comparison, reconciliation, projection logic, and tests in `apps/web/lib/...`. It does not change an API request or response shape, route, webhook, status code, environment/config key, database schema, or SDK signature in the listed public packages. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The description contains one magic-word pair: `Fixes ENG-3950` on the ticket line at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Coverage Accounts For The Whole Diff **Explanation** The description has a non-empty Coverage table and an explicit Open gaps section with `none`. It covers the changed behaviors: exact expected edges pass without repair, missing or unexpected edges trigger repair and block activation, and edge order or subject-relation changes affect set matching. Each unit row has a named rerun command above the table, the mutation row names `apps/web/lib/authzed/survey-relationships.ts:82`, and every row states an outcome. The diff changes no rendered UI. Comment `@the reviewer help` to get the list of available commands.
#9662
2026-10-20 22:07
cubic
review-body
—summary
**No issues found** across 5 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9662
2026-10-20 22:08
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge within the validated scope: correct survey graphs pass, while real drift still requires repair and another verification pass. Reviews (1) · Last reviewed commit: "fix(upgrade): compare survey edges as se..." · Reviewed by the reviewer
#9663
2026-10-20 22:10
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough Online upgrade validation no longer requires at least two Cube replicas. It continues to require at least two fixed Hub replicas and rejects Hub autoscaling. The tests use a single-replica Cube fixture and add rejection checks for invalid Hub replica counts and multiple Cube replicas with the in-memory driver. The configuration documentation describes these requirements and the Cube Store requirement for multiple Cube replicas. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to f8f06 Online upgrades can now use the default single Cube replica, while Hub replica requirements remain enforced. No merge-blocking risk
… the rest (5434 characters) is evident. Security architecture risk: 🔵 Low · up to f8f06 The change preserves Hub availability requirements and existing security controls. No introduced security defect was established. Risk remains low rather than minimal because safe overlap of old and new in-memory Cube instances has not been demonstrated by the available runtime evidence. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _observed_ — The existing coordinator has namespace-scoped deployment creation and Pod patch authority, alongside named workload and ingress-proxy controls. That authority predates this PR; relaxing Cube eligibility does not change the Role or service-account binding. The documented exposure remains administrative access to the application namespace. **Trust Boundaries and Controls** - _observed_ — Online mode retains existing-installation checks, ownership of migration-mode environment variables, enabled target workers, fixed web capacity, the required readiness path, and explicit ingress inventory. The changed replica gate does not remove those controls. **Resilience and Maintainability Implications** - _inferred_ — Zero-unavailable rolling replacement and readiness probes support serving-capacity preservation, but do not alone prove safe transfer or duplication of in-memory cache/queue work. The unresolved issue is runtime compatibility during overlap, not a demonstrated authorization bypass or persistent-state corruption. **Hardening Proposals** - _proposed_ — Validate the singleton memory-driver path in a released-image Kubernetes rehearsal, including old/new Cube overlap, active query polling, tenant isolation, interruption, and retry. Use that evidence to establish whether transient independent caches and queues are supported before relying on an uninterrupted-upgrade guarantee. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits syntax, uses the allowed fix type and chart scope, describes the single Cube replica change, uses lowercase text, and includes the ENG-3955 reference. | | Description check | | The description includes the ticket reference, change summary, implementation locations, breaking-change assessment, migration status, test command, coverage, gaps, risks, and AI metadata. It clearly … | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The Breaking changes checkbox is unticked. The diff changes only online-upgrade validation, tests, and documentation. It relaxes the existing Cube replica validation and does not rename or retype an A… | | Only The Ticket Line Carries A Magic Word | | The raw PR description contains one magic-word pair: “Fixes ENG-3955” on the ticket line at the top. No other “Fixes”, “Closes”, “Resolves”, or “Ref” appears next to an ENG ticket ID or GitHub issue r… | | Coverage Accounts For The Whole Diff | | The description covers the changed behaviors. The Coverage table verifies default single-Cube Helm and Argo rendering, Hub replica rejection, the retained multi-replica in-memory Cube guard, rule-spec… | Full details: Description check **Explanation** The description includes the ticket reference, change summary, implementation locations, breaking-change assessment, migration status, test command, coverage, gaps, risks, and AI metadata. It clearly matches the pull request changes. Full details: Breaking Changes Match The Diff **Explanation** The Breaking changes checkbox is unticked. The diff changes only online-upgrade validation, tests, and documentation. It relaxes the existing Cube replica validation and does not rename or retype an API field, remove an endpoint, change an HTTP status, change a default or environment/config key, add a manual database migration, or change the specified SDK surface. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The raw PR description contains one magic-word pair: “Fixes ENG-3955” on the ticket line at the top. No other “Fixes”, “Closes”, “Resolves”, or “Ref” appears next to an ENG ticket ID or GitHub issue reference. The changed files also contain no such pair. Full details: Coverage Accounts For The Whole Diff **Explanation** The description covers the changed behaviors. The Coverage table verifies default single-Cube Helm and Argo rendering, Hub replica rejection, the retained multi-replica in-memory Cube guard, rule-specific negative cases, and the manual upgrade path. Each row has a method and outcome; the shared Rerun command names charts/formbricks/tests/online-upgrade.sh. The mutation row names charts/formbricks/templates/online-upgrade.yaml:19. Open gaps is present and says none. The diff changes chart validation and documentation, not rendered UI, so no screenshot is required. Comment `@the reviewer help` to get the list of available commands.
#9663
2026-10-20 22:11
cubic
inline · P2
charts/formbricks/templates/online-upgrade.yaml:19defect-functional
Removing the Cube check also removes the only guard against `cube.replicas: 0`, which now renders online mode with no Cube pods. Keep a minimum of one Cube replica by adding a `cube.replicas >= 1` fail alongside the Hub check. ```suggestion {{- if or .Values.hub.autoscaling.enabled (lt (int .Values.hub.replicas) 2) }}{{ fail "Online mode requires fixed hub.replicas >= 2" }}{{ end }} {{- if lt (int .Values.cube.replicas) 1 }}{{ fail "Online mode requires at least one Cube replica" }}{{ end }} ```
#9663
2026-10-20 22:11
cubic
inline · P2
docs/self-hosting/configuration/online-upgrade.mdx:29docs
This rollout also needs capacity for an extra Cube pod: `maxSurge: 1` cannot create the replacement otherwise, so the Helm upgrade stalls while waiting for readiness. Add the Cube and Hub surge-pod capacity to the upgrade prerequisites.
#9663
2026-10-20 22:11
cubic
inline · P2
charts/formbricks/templates/online-upgrade.yaml:19defect-functional
This exemption admits the default memory-backed Cube, but an online Cube image or configuration rollout starts a second pod because `maxSurge` is 1. That violates the documented single-replica driver constraint and can route requests to independent in-memory queues; keep Cube rollouts single-pod or require Cube Store when Cube changes.
#9663
2026-10-20 22:11
cubic
review-body
—summary
**3 issues found** across 3 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9663
2026-10-20 22:19
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The change reaches the online Kubernetes upgrade workflow\. Require at least one Cube replica before merging; restoring Cube Store success coverage is a separate, non-blocking improvement. Findings 1. **Cube can reach zero replicas** 2. **Cube Store coverage was removed** Reviews (1) · Last reviewed commit: "fix(chart): let online mode keep a singl..." · Reviewed by the reviewer
#9663
2026-10-20 22:19
greptile
inline · P1
charts/formbricks/templates/online-upgrade.yaml:19defect-functional
**Cube can reach zero replicas** Removing the Cube replica check also allows `cube.replicas=0` during an online upgrade. The chart renders a Cube Deployment with zero replicas, so applying it to a healthy installation stops every Cube pod and makes Cube-backed analytics unavailable. `maxUnavailable: 0` does not prevent an explicit scale-down, and the dependency health check accepts zero ready replicas when zero are requested. Keep a lower bound of one Cube replica while allowing the default single-replica setup. This availability guard needs to be restored before merging. Artifacts **Evidence from the check** - The executed Python harness contains all Helm commands and the Node exact-me
… the rest (729 characters)thod extraction used for both revisions, making the focused reproduction repeatable. **Command output from the check** - Captured base Helm and Argo renders with zero- and one-replica inputs show rejection by the original Hub-and-Cube guard. **Command output from the check** - Captured head renders and exact-method executions show zero desired replicas accepted as healthy, while unhealthy positive-replica and stale-generation controls are rejected. **Command output from the check** - Captured source-path and environment commands show both dependency-check callers, unchanged tracked files, and missing cluster configuration, limiting proof to rendering and mocked health responses. Ran code and verified through TREX
#9663
2026-10-20 22:19
greptile
inline · P2
charts/formbricks/tests/online-upgrade.sh:39tests
**Cube Store coverage was removed** Switching the shared fixture to the default Cube setup removes the successful render test for multiple Cube replicas with Cube Store. This is a non-blocking regression-coverage gap: the updated suite passes even when the chart is changed to reject every multi-replica Cube configuration, whereas the previous suite catches that change. Keep the new default case and add a successful render with `cube.replicas=2` and `CUBEJS_CACHE_AND_QUEUE_DRIVER=cubestore` for Helm and Argo. Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! Artifacts **Evidence from the check** - Authored and
… the rest (709 characters) executed harness archives both revisions, runs their suites, and applies the same isolated mutation to demonstrate the lost coverage. **Command output from the check** - Executed the base suite and Helm/Argo fixture renders before and after mutation; the previous successful fixture catches the invalid rejection. **Command output from the check** - Executed the head suite and Helm/Argo fixture renders before and after mutation; the suite passes despite both supported Cube Store renders failing. **Command output from the check** - Captured the harness command, working directory, exit code, and assertion result; the single validation run completed successfully. Ran code and verified through TREX
#9647
2026-10-20 22:20
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The integration test now identifies the persisted bootstrap administrator by its marker. It verifies that exactly one fulfilled sign-up result contains that administrator’s ID. The test no longer requires the other concurrent request to reject. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to 6e4fc The revised race test preserves the persisted single-administrator checks while allowing either supported outcome for the losing sign-up. No issue identified here blocks merging. Architecture risk: 🔵 Low · up to 6e4fc The change affects 1 system. **Changed systems:** `apps/web` **Architecture concerns** No archi
… the rest (4797 characters)tecture-level concerns identified. Review details **Systems and components** - _observed_ — apps/web (ui) was modified; 1 changed file maps to changed impact. **Before / after behavior** - _observed_ — Modified behavior in apps/web/integration/signup-bootstrap-race.integration.test.ts: The concurrent sign-up setup remains, but the bootstrap-user assertion now queries the marked user record and verifies that exactly one fulfilled outcome references its persisted ID. The previous exact fulfilled/rejected outcome counts were removed because the losing request may reject or produce a duplicate-style fulfilled response depending on whether the policy check or later creation check observes the winner. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits with the valid type and scope, uses a lower-case descriptive summary without a trailing period, and includes the ENG-3980 reference. It accurately describes the … | | Description check | | The description is complete and aligned with the template. It includes the ticket reference, Was/Now summary, review links, breaking-change status, migration and environment impact, test command, cove… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description unticks “This PR contains breaking changes.” The authoritative diff changes only `apps/web/integration/signup-bootstrap-race.integration.test.ts`, updating test assertions and comme… | | Only The Ticket Line Carries A Magic Word | | The PR description contains one closing-keyword pair: `Fixes ENG-3980` on the ticket line at the top. Other references, including `#9186`, `#9536`, `ENG-2247`, and `ENG-2812`, have no `Fixes`, `Closes… | | Coverage Accounts For The Whole Diff | | PASS. The diff changes only `apps/web/integration/signup-bootstrap-race.integration.test.ts`; the description explicitly states “Test-only change.” The Coverage table covers the masked-200 race, the u… | Full details: Title check **Explanation** The title follows Conventional Commits with the valid type and scope, uses a lower-case descriptive summary without a trailing period, and includes the ENG-3980 reference. It accurately describes the test flake fix. Full details: Description check **Explanation** The description is complete and aligned with the template. It includes the ticket reference, Was/Now summary, review links, breaking-change status, migration and environment impact, test command, coverage, open gaps, risks, and agent details. Full details: Breaking Changes Match The Diff **Explanation** The PR description unticks “This PR contains breaking changes.” The authoritative diff changes only `apps/web/integration/signup-bootstrap-race.integration.test.ts`, updating test assertions and comments. It does not change an API shape, route, status behavior, configuration, database schema, or public SDK signature. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The PR description contains one closing-keyword pair: `Fixes ENG-3980` on the ticket line at the top. Other references, including `#9186`, `#9536`, `ENG-2247`, and `ENG-2812`, have no `Fixes`, `Closes`, `Resolves`, or `Ref` next to them. Full details: Coverage Accounts For The Whole Diff **Explanation** PASS. The diff changes only `apps/web/integration/signup-bootstrap-race.integration.test.ts`; the description explicitly states “Test-only change.” The Coverage table covers the masked-200 race, the unique-index regression, the missing-marker regression, and the 422 backstop behavior. Each row gives a verification method and observed outcome. The `Rerun:` command names the integration spec, and both mutation rows identify editable `file:line` targets. The Open gaps subsection is present and records ENG-2812 and the Node-version gap. No rendered UI changes require media. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 1 - Waived: $0.25 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9647
2026-10-20 22:20
cubic
review-body
—summary
**No issues found** across 1 file View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9647
2026-10-20 22:26
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Low impact]** Safe to merge. The changed assertion preserves the database invariants without depending on how the losing sign-up responds. Reviews (1) · Last reviewed commit: "test(auth): stop the bootstrap-race test..." · Reviewed by the reviewer
#9632
2026-10-20 22:28
coderabbit
issue-comment
—summary
### Walkthrough Webhook survey selection now uses `SurveySelect` in the add and settings screens. The component supports all-survey selection, restricted-survey guidance, and preservation of attached restricted surveys for removal. New utilities map survey options and merge selected IDs, with tests for these behaviors. `MultiSelect` removal buttons now use a translated accessible label. Priority: ⬇️ Low Merge Risk: 🔵 Low · up to f21e9 The survey picker works as intended, but screen-reader and keyboard users get a mislabeled input and non-functional remove buttons in read-only mode. These are small, localized fixes and do not block merge. Security architecture risk: ⚪ Minimal · up to f2
… the rest (6647 characters)1e9 The changed selection flow preserves existing webhook targets, restricted-survey controls, and save behavior. No material security risk introduced or worsened by this PR was identified. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The security-relevant changed behavior is bounded to webhook target editing through existing create and update actions. The reviewed changes do not establish expanded tenant reach, additional privileges, or a new outbound execution path. **Trust Boundaries and Controls** - _observed_ — Disabled picker options provide client-side guidance, not server authority. Server attachment checks independently reject newly attached restricted surveys and permit existing attachments to remain. This distinction predates the PR; the new picker does not replace the server check. Pre-merge checks: 6 · 1 Failed checks: 1 warning | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Coverage Accounts For The Whole Diff | | The description has a non-empty Coverage table, named unit tests with mutation file:line targets, observed outcomes, screenshots, and an Open gaps entry. However, it does not account for all changed b… | Add Coverage rows with a verification method and observed outcome for selecting, removing, and searching surveys; toggling “All current and new surveys” to verify that the selection clears and the combobox disables; and the new `MultiSelect… | Passed checks: 6 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits format, uses the valid `fix` type and `webhooks` scope, describes the multi-select survey change, uses lower-case wording, and appends a valid ENG-3960 reference… | | Description check | | The description includes the required ticket reference, What & why, review locations, breaking-change status, migrations, test command, coverage, open gaps, risks, and AI note. It clearly matches the … | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves the breaking-change checkbox unticked. The diff changes only internal web UI components, locale entries, tests, and webhook picker logic. The Add Webhook and Settings paths s… | | Only The Ticket Line Carries A Magic Word | | The only magic-word/ticket pair in the raw description is “Fixes ENG-3960” on the top ticket line. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-<number>` or `#<number>` ref… | Full details: Title check **Explanation** The title follows Conventional Commits format, uses the valid `fix` type and `webhooks` scope, describes the multi-select survey change, uses lower-case wording, and appends a valid ENG-3960 reference. Full details: Description check **Explanation** The description includes the required ticket reference, What & why, review locations, breaking-change status, migrations, test command, coverage, open gaps, risks, and AI note. It clearly matches the changeset. The appended generated summary is extra but does not prevent review. Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves the breaking-change checkbox unticked. The diff changes only internal web UI components, locale entries, tests, and webhook picker logic. The Add Webhook and Settings paths still send `surveyIds`; no endpoint, webhook payload shape, status code, environment key, migration, database schema, or SDK surface in `packages/js-core`, `packages/survey-ui`, or `packages/surveys` changes. The added locale keys are additive. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The only magic-word/ticket pair in the raw description is “Fixes ENG-3960” on the top ticket line. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Coverage Accounts For The Whole Diff **Explanation** The description has a non-empty Coverage table, named unit tests with mutation file:line targets, observed outcomes, screenshots, and an Open gaps entry. However, it does not account for all changed behavior. The diff adds MultiSelect selection and removal behavior, including the new `aria-label` from `common.remove_item`, and makes the survey combobox clear and disable when “all” is selected. The manual row only reports that the checkbox and combobox render and that the dropdown opens. The utility tests cover option generation and ID merging, not these component interactions or the new accessibility label. The Settings-tab browser check is listed as an Open gap, but no row or gap covers the other missing behaviors. **Resolution** Add Coverage rows with a verification method and observed outcome for selecting, removing, and searching surveys; toggling “All current and new surveys” to verify that the selection clears and the combobox disables; and the new `MultiSelect` removal-button accessible label. Name the relevant test/spec and rerun command for unit or e2e rows. If these checks were not performed, list each behavior under Open gaps with a reason. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 7 - Waived: $1.75 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9632
2026-10-20 22:30
coderabbit
inline · P2
apps/web/modules/integrations/webhooks/components/survey-select.tsx:66defect-functional
**🎯 Functional Correctness** | **🟡 Minor** | **⚡ Quick win** **Connect the Surveys label to the picker input.** Both webhook screens render `<Label htmlFor="Surveys">`, but `MultiSelect` gives its `CommandPrimitive.Input` no matching `id`. The visible label therefore does not identify the new input programmatically. Pass an input ID through `MultiSelect` and use it for the label and input. As per path instructions, components must “maintain basic accessibility.” _Source: Path instructions_
#9632
2026-10-20 22:30
coderabbit
inline · P2
apps/web/modules/integrations/webhooks/components/survey-select.tsx:78defect-functional
**🎯 Functional Correctness** | **🟡 Minor** | **⚡ Quick win** **Disable removal buttons when the picker is disabled.** In read-only Settings, existing surveys remain visible as chips. `MultiSelect` disables its input, but each chip still has a focusable removal button labeled “Remove …”; activating the button does nothing because `handleUnselect` returns when disabled. Pass `disabled` to those buttons so their state matches their behavior. Based on learnings, a disabled component state must also reach its accessible control state. _Source: Learnings_
#9632
2026-10-20 22:30
coderabbit
review-body
—summary
**Actionable comments posted: 2**
#9632
2026-10-20 22:35
cubic
inline · P2
apps/web/locales/fr-FR.json:486docs
`Supprimer` suggests deleting the survey, but this button only removes it from the current selection; use `Retirer {item}` to match the existing French `remove` label. ```suggestion "remove_item": "Retirer {item}", ```
#9632
2026-10-20 22:35
cubic
inline · P2
apps/web/modules/integrations/webhooks/components/survey-select.tsx:41defect-functional
After a user removes an attached restricted survey, this still treats the original `webhook.surveyIds` as attached, so the option remains enabled and can be re-added; `RestrictedSurveysNote` also misses that it is now restricted. Derive the exemption from the current `selectedSurveys` and pass it to both the option builder and note.
#9632
2026-10-20 22:35
cubic
inline · P3
apps/web/modules/integrations/webhooks/components/survey-select.tsx:78defect-functional
Disable the chip-removal buttons whenever `MultiSelect` is disabled; in read-only settings they remain focusable even though their handler does nothing.
#9632
2026-10-20 22:35
cubic
review-body
—summary
**3 issues found** across 23 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9632
2026-10-20 22:37
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The change reaches webhook editing and other screens that use the shared multi\-select\. The reproduced keyboard usability regression is non-blocking; no blocking merge-safety issue was established. Findings 1. **Same\-named surveys highlight together** Reviews (1) · Last reviewed commit: "fix(webhooks): pick webhook surveys with..." · Reviewed by the reviewer
#9632
2026-10-20 22:37
greptile
inline · P2
apps/web/modules/integrations/webhooks/components/survey-select.tsx:67defect-functional
**Same-named surveys highlight together** When two surveys share a name, the new picker highlights both rows at once because the shared `MultiSelect` omits `value` on `CommandItem`, causing cmdk to use the displayed name instead of the unique survey ID. Repeated ArrowDown presses leave both rows highlighted, and Enter selects the first survey. This is a non-blocking keyboard usability regression: users cannot identify a unique active survey, and forward navigation can stall on the duplicate pair. Pass `value={option.value}` to `CommandItem` and add a duplicate-name keyboard interaction test. Artifacts **Recording of the check** - Ran the parent-revision picker with duplicate survey names
… the rest (2850 characters) and used Tab and Space; each checkbox received independent focus and the second survey was selected. **Second duplicate checkbox focused before the PR** - Captured the parent picker after tabbing to the second same-named survey; browser state identifies survey-beta as the sole focused checkbox. **Recording of the check** - Focused the real PR combobox, pressed ArrowDown twice, and pressed Enter; both duplicate rows remained active before only the first survey ID was selected. **Both duplicate rows active after ArrowDown** - Captured the changed picker after ArrowDown with live browser-state diagnostics; both same-named rows report selected=true. **Command output from the check** - Recorded the executed command, working directory, exit code, revision, browser version, and interaction observations; duplicate active states were reproduced with no tracked-source changes. **Command output from the check** - Captured the runner’s original stdout and stderr before the shell added command metadata to the companion log; the runtime assertions passed. **Evidence from the check** - Saved browser-measured focus, item values, selected attributes, computed backgrounds, and selected survey IDs for each interaction; the duplicate-value collision is recorded directly. **Evidence from the check** - Extracted an unmodified short substring from the installed cmdk module with source location and command metadata; item selected state compares the stored value with the item value. **Evidence from the check** - Preserved the exact shell script used to run the validation and capture exit status and output; the run is reproducible. **Evidence from the check** - Preserved the full executed runner that bundles actual components, serves the harness, drives Chromium, extracts cmdk source, and records evidence; no product implementation was substituted. **Evidence from the check** - Preserved the full React harness with two same-named surveys, parent and PR component imports, translation setup, and selected-ID output; both revisions received equivalent fixture state. **Evidence from the check** - Extracted the original SurveyCheckboxGroup directly from HEAD’s parent and executed it in the harness; the before capture uses repository source rather than a recreation. **Evidence from the check** - Saved the complete generated JavaScript served to Chromium, including the actual picker and shared components; the executed browser source is available. **Evidence from the check** - Compiled repository global Tailwind styles and added harness-only layout styling; production selected-state styling remained in use. **Evidence from the check** - Saved the exact page shell loading the generated component bundle and stylesheet; both recordings used the same browser page structure. Ran code and verified through TREX
#9622
2026-10-20 22:39
coderabbit
issue-comment
—summary
### Walkthrough The change adds `keepEndingCardOpen` as a survey setting with a default of `false`. The setting is stored and included in survey selections, workspace state, API schemas, and editor controls. Completion handling now uses a shared helper to choose immediate closure, a three-second delay, or no automatic closure based on survey mode, ending configuration, and the setting. Priority: ➖ Normal Merge Risk: 🔵 Low · up to 297b9 The new ending-card setting is mostly consistent across storage, APIs, and runtime behavior. The API docs should say that the close behavior applies to modal surveys, not inline embeds. This is a small documentation fix and does not block merging. Securi
… the rest (7111 characters)ty architecture risk: 🔵 Low · up to 297b9 The setting is default-off, remains behind existing survey-management permissions, and changes presentation timing without delaying response completion. The main remaining uncertainty is coordinating the database migration with application rollout. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The changed capability affects how long respondents see ending content for a survey an authorized writer can already manage. In the traced path, it neither grants additional privileges nor changes response-data access or cross-workspace ownership. **Trust Boundaries and Controls** - _observed_ — V3 creation requires read-write workspace access and binds persistence to the authorized workspace. PATCH resolves the stored survey and requires workspace access plus survey.write authorization before applying the new scalar. Strict boolean validation prevents this field from carrying arbitrary structured input. **Resilience and Maintainability Implications** - _observed_ — The renderer clears owned close timers on manual dismissal and unmount. Leaving the first block now stops the inactivity countdown, preventing that timer from dismissing a kept-open ending. Existing close-button, Escape, and configured outside-click paths remain available independently of automatic dismissal. Pre-merge checks: 4 · 2 · 1 Failed checks: 2 warnings · 1 inconclusive | Check name | Status | Explanation | Resolution | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Breaking Changes Match The Diff | | The Breaking changes checkbox is unticked, but the diff adds a required API v2 response field. `packages/database/zod/surveys.ts:100-102` makes `keepEndingCardOpen` required in `ZSurveyWithoutQuestion… | Change the PR description checkbox to `- [x] This PR contains breaking changes` and document that the API v2 survey response now requires `keepEndingCardOpen`. | | Coverage Accounts For The Whole Diff | | The coverage table documents ending-close logic, v3 API handling, the editor toggle, and manual widget behavior, but it does not cover all changed behavior. The diff changes survey-copy behavior by se… | Update `## How this was tested` with coverage for survey copying and the v2 management input, naming the relevant test/spec or command and the observed result. Alternatively, list each unverified behavior under `Open gaps` with a reason. Ke… | | Only The Ticket Line Carries A Magic Word | | The visible description contains the allowed top-line pair, “Fixes ENG-3428”. The description is explicitly truncated, so the remainder could contain another `Fixes`, `Closes`, `Resolves`, or `Ref` pa… | Provide the complete raw pull request description and rescan every line for magic-word pairs outside the top ticket line. | Passed checks: 4 passed | Check name | Status | Explanation | | :------------------------ | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits syntax, uses the valid `feat` type and optional scope, describes the change, uses lowercase text without a trailing period, and includes the ENG-3428 reference. | | Description check | | The description includes the required ticket, What & why, review locations, breaking-change status, migration details, test coverage, open gaps, risks, and agent note. It clearly explains the user-vis… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | Full details: Description check **Explanation** The description includes the required ticket, What & why, review locations, breaking-change status, migration details, test coverage, open gaps, risks, and agent note. It clearly explains the user-visible behavior and implementation. Full details: Breaking Changes Match The Diff **Explanation** The Breaking changes checkbox is unticked, but the diff adds a required API v2 response field. `packages/database/zod/surveys.ts:100-102` makes `keepEndingCardOpen` required in `ZSurveyWithoutQuestionType`, which is used by the v2 survey response schemas. `docs/api-v2-reference/openapi.yml:5831` adds the field to the survey schema's required list. This is not a purely optional additive response field. The migration default and optional request fields do not remove this response-shape break. **Resolution** Change the PR description checkbox to `- [x] This PR contains breaking changes` and document that the API v2 survey response now requires `keepEndingCardOpen`. Full details: Coverage Accounts For The Whole Diff **Explanation** The coverage table documents ending-close logic, v3 API handling, the editor toggle, and manual widget behavior, but it does not cover all changed behavior. The diff changes survey-copy behavior by selecting `keepEndingCardOpen` in `apps/web/modules/survey/list/lib/survey.ts:100`; the table has no copy row or corresponding open gap. It also adds the accepted and partial `keepEndingCardOpen` field to the v2 management input at `apps/web/modules/api/v2/management/surveys/types/surveys.ts:40,63`, which is not covered by the v3 row. The reported open gaps concern the prefilled-block countdown and v2 OpenAPI generation instead. **Resolution** Update `## How this was tested` with coverage for survey copying and the v2 management input, naming the relevant test/spec or command and the observed result. Alternatively, list each unverified behavior under `Open gaps` with a reason. Keep the existing screenshot evidence and countdown gap. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The visible description contains the allowed top-line pair, “Fixes ENG-3428”. The description is explicitly truncated, so the remainder could contain another `Fixes`, `Closes`, `Resolves`, or `Ref` pair with `ENG-&lt;number&gt;` or `#&lt;number&gt;`. The review refs contain only the commit message, not the full PR description. **Resolution** Provide the complete raw pull request description and rescan every line for magic-word pairs outside the top ticket line. Comment `@the reviewer help` to get the list of available commands.
#9622
2026-10-20 22:42
coderabbit
inline · P2
docs/api-v3-reference/src/components/schemas/SurveyDistribution.yml:61docs
**🎯 Functional Correctness** | **🟡 Minor** | **⚡ Quick win** **Limit the close-behavior description to modal surveys.** When a survey runs in inline mode, `getEndingCloseDelay` returns `null` before it checks the ending cards or `keepEndingCardOpen`. The documented three-second and immediate-close behaviors therefore do not apply to inline embeds. Specify that these behaviors apply to modal surveys, then regenerate `docs/api-v3-reference/openapi.yml`.
#9622
2026-10-20 22:42
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9622
2026-10-20 22:47
cubic
review-body
—summary
**3 issues found** across 45 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9622
2026-10-20 22:47
cubic
inline · P2
packages/surveys/src/lib/ending-close.ts:26defect-functional
This checks the first configured ending rather than the ending actually shown. When a quota selects an end-screen but the first ending is a redirect, the modal never auto-closes even though the respondent sees a regular ending card; base the decision on the selected ending.
#9622
2026-10-20 22:47
cubic
inline · P2
docs/api-v3-reference/src/components/schemas/SurveyDistribution.yml:61docs
The no-ending case is not always immediate: inline surveys remain open for the host to close, and the helper returns before checking for missing endings. Qualify the stated close behavior as modal-only and document inline closure separately. ```suggestion Keep the ending card on screen until the respondent closes it. For modal app surveys, when false, the widget closes 3 seconds after the ending card appears; without an ending, it closes immediately. Inline surveys are closed by the host; the first ending redirect is not auto-closed. Independent of `autoClose`. ```
#9622
2026-10-20 22:47
cubic
inline · P3
apps/web/locales/zh-Hant-TW.json:3922quality
This string uses a half-width comma after "畫面上", while the rest of zh-Hant-TW.json consistently uses full-width punctuation (,/。). Use full-width "," to match the file's convention. ```suggestion "keep_ending_card_open_description": "讓結尾卡片持續顯示在畫面上,直到受訪者自行關閉,而不是在卡片出現 3 秒後自動關閉問卷。", ```
#9622
2026-10-20 22:51
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** The change is safe to merge within the validated scope; no blocking issues were found. Reviews (1) · Last reviewed commit: "feat(surveys): let creators keep the end..." · Reviewed by the reviewer
#9624
2026-10-20 22:52
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The change adds the exported `forEachConcurrently` helper. It validates the concurrency limit, processes all items with bounded concurrency, and throws an `AggregateError` after task failures. Source-file scanning now reads files with a concurrency limit of 32. Per-file read errors continue to be logged without stopping other files. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to f79cf The faster scan preserves the validation results and displayed key order. No issue identified here needs to be resolved before merging. Pre-merge checks: 6 · 1 Failed checks: 1 warning | Check name | Status | Explanation | Re
… the rest (4699 characters)solution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Coverage Accounts For The Whole Diff | | The Coverage table is populated and covers the changed helper and scan behavior. The Rerun command names `src/scan-translations.test.ts`, each row has a method and outcome, and `Open gaps` explicitly … | Update the two mutation rows to include the complete edit locations. For example, name `packages/i18n-utils/src/scan-translations.ts:183` for the concurrency validation mutation and `packages/i18n-utils/src/scan-translations.ts:192` for the… | Passed checks: 6 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title uses the required Conventional Commits format, describes the bounded concurrent file-reading change, uses lower-case summary text, and includes a valid Linear ticket reference. | | Description check | | The description includes the required ticket reference, change summary, review location, breaking-change status, migration details, test command, coverage, open gaps, risks, and AI model note. It is c… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR declares no breaking changes with an unticked checkbox. The diff only adds tests and adds `forEachConcurrently` plus bounded reads in `packages/i18n-utils/src/scan-translations.ts`; it changes … | | Only The Ticket Line Carries A Magic Word | | The only magic-word pair in the raw PR description is the opening ticket line, “Fixes ENG-3936”. The later references “ENG-3871 (`#9598`)” and “#9598” have no adjacent `Fixes`, `Closes`, `Resolves`, or … | Full details: Description check **Explanation** The description includes the required ticket reference, change summary, review location, breaking-change status, migration details, test command, coverage, open gaps, risks, and AI model note. It is complete and directly related to the changes. Full details: Breaking Changes Match The Diff **Explanation** The PR declares no breaking changes with an unticked checkbox. The diff only adds tests and adds `forEachConcurrently` plus bounded reads in `packages/i18n-utils/src/scan-translations.ts`; it changes no request/response shape, route, webhook, status code, config or environment key, database schema, or SDK export in `packages/js-core`, `packages/survey-ui`, or `packages/surveys`. The added i18n-utils helper and internal scan behavior are outside the defined breaking-change list. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The only magic-word pair in the raw PR description is the opening ticket line, “Fixes ENG-3936”. The later references “ENG-3871 (`#9598`)” and “#9598” have no adjacent `Fixes`, `Closes`, `Resolves`, or `Ref` word. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table is populated and covers the changed helper and scan behavior. The Rerun command names `src/scan-translations.test.ts`, each row has a method and outcome, and `Open gaps` explicitly says `none`. However, two rows claim `unit (mutation)` without naming a complete mutated `file:line`: the invalid-concurrency row says only `same file`, and the rejection row says only `:192`. The mutation requirement is therefore not met for those rows. **Resolution** Update the two mutation rows to include the complete edit locations. For example, name `packages/i18n-utils/src/scan-translations.ts:183` for the concurrency validation mutation and `packages/i18n-utils/src/scan-translations.ts:192` for the `try/catch` mutation. Keep the existing test command and observed outcomes. Comment `@the reviewer help` to get the list of available commands.
#9624
2026-10-20 22:57
cubic
review-body
—summary
**No issues found** across 2 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9624
2026-10-20 23:02
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Low impact]** Safe to merge within the reviewed scope. Executed checks support unchanged key membership, bounded concurrent reads, and continued scanning after individual read failures. Reviews (1) · Last reviewed commit: "perf(i18n-utils): read source files thro..." · Reviewed by the reviewer
#9658
2026-10-20 23:03
coderabbit
issue-comment
—summary
### Walkthrough The online-upgrade guide now requires matching Hub and Cube image digests between the 6.0.x source release and the 6.1 configuration. It documents the image requirements for stock 6.0.x releases, including the bundled Cube image requirement for 6.0.0 and 6.0.1. It also provides example values for Hub 0.14.0 and Cube 1.7.47-1, and explains that the same digests populate the dependency inventory automatically. Priority: ➖ Normal **Severity of issue fixed:** Medium Merge Risk: 🟡 Moderate · up to a595d The upgrade guide's Cube example works for 6.0.x. However, operators who carry those values into the 6.1 online upgrade will produce an invalid Cube image and block the upgra
… the rest (5604 characters)de. The guide should tell them to restore the plain Cube repository before this lands. Architecture risk: 🔵 Low · up to a595d The change affects 1 system. **Changed systems:** `docs` **Architecture concerns** No architecture-level concerns identified. Review details **Systems and components** - _observed_ — docs (service) was modified; 1 changed file maps to changed impact. **Before / after behavior** - _observed_ — Modified behavior in docs/self-hosting/configuration/online-upgrade.mdx: Added source-release requirements for pinning Hub and Cube to matching immutable images before the online upgrade. Stock 6.0.x releases fail preparation with non-retryable `dependency_image_changed`; 6.0.0 and 6.0.1 additionally require switching from `cubejs/cube:v1.6.6` to the bundled Cube image. - _observed_ — Modified behavior in docs/self-hosting/configuration/online-upgrade.mdx: Added a YAML example configuring Hub 0.14.0 and Cube 1.7.47-1 by digest, with Cube’s digest split between repository and tag fields. - _observed_ — Modified behavior in docs/self-hosting/configuration/online-upgrade.mdx: Added instructions to reuse the same Hub and Cube digests in the 6.1 online configuration. These values populate the dependency inventory automatically, and Cube’s default 1.7.50-1 must be overridden with the 1.7.47-1 digest. Pre-merge checks: 6 · 1 Failed checks: 1 warning | Check name | Status | Explanation | Resolution | | :--------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Description check | | The description includes the required sections and testing details, but it incorrectly marks the change as non-breaking and lists no migration or cutover step even though affected self-hosters must ma… | Tick the breaking-changes checkbox and add the required table describing the manual image-pinning action. Update Migrations & env to document this upgrade step instead of saying none. | Passed checks: 6 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits syntax, uses a valid scope, stays lowercase, has no trailing period, and accurately describes the documentation change. | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves `- [ ] This PR contains breaking changes` unticked. The authoritative diff changes only `docs/self-hosting/configuration/online-upgrade.mdx` and adds operational guidance; it… | | Only The Ticket Line Carries A Magic Word | | The description contains one magic-word pair: “Fixes ENG-3956” on the ticket line at the top. Other text contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with an `ENG-<number>` or `#… | | Coverage Accounts For The Whole Diff | | The diff changes only `docs/self-hosting/configuration/online-upgrade.mdx`, so it changes no product behavior. The description states `None — docs only` under Breaking changes. The `How this was teste… | Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves `- [ ] This PR contains breaking changes` unticked. The authoritative diff changes only `docs/self-hosting/configuration/online-upgrade.mdx` and adds operational guidance; it does not change an API shape, route, status, environment variable/config default, database schema, or public SDK signature. No listed breaking change is present. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The description contains one magic-word pair: “Fixes ENG-3956” on the ticket line at the top. Other text contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Coverage Accounts For The Whole Diff **Explanation** The diff changes only `docs/self-hosting/configuration/online-upgrade.mdx`, so it changes no product behavior. The description states `None — docs only` under Breaking changes. The `How this was tested` section also includes a non-empty manual Coverage row with a verification method and observed outcome, and it includes an explicit Open gaps subsection. Full details: Description check **Explanation** The description includes the required sections and testing details, but it incorrectly marks the change as non-breaking and lists no migration or cutover step even though affected self-hosters must manually pin images before upgrading. **Resolution** Tick the breaking-changes checkbox and add the required table describing the manual image-pinning action. Update Migrations & env to document this upgrade step instead of saying none. Comment `@the reviewer help` to get the list of available commands.
#9658
2026-10-20 23:04
cubic
inline · P2
docs/self-hosting/configuration/online-upgrade.mdx:61docs
This overstates the validation: the pair has not been verified through a live upgrade, despite the page's warning that released-image rehearsals are still outstanding. Describe these as the selected images, not as a verified serving-through-upgrade pair. ```suggestion 1.7.47-1 are the selected images for an upgrade from 6.0.2. 6.0.0 and 6.0.1 still ```
#9658
2026-10-20 23:04
cubic
review-body
—summary
**1 issue found** across 1 file Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9658
2026-10-20 23:06
coderabbit
inline · P1
docs/self-hosting/configuration/online-upgrade.mdx:78docs
**🎯 Functional Correctness** | **🟠 Major** | **⚡ Quick win** **Restore the Cube repository in the 6.1 values.** If an operator carries the example values into 6.1, setting `cube.image.digest` alone makes the 6.1 helper render `ghcr.io/formbricks/cube@sha256@sha256:3859…`, not a valid image reference. Helm supports carrying release values forward, so this is a feasible upgrade path. Tell operators to set `cube.image.repository: ghcr.io/formbricks/cube` in the 6.1 values while retaining the same digest. (docs.helm.sh)
#9658
2026-10-20 23:06
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9658
2026-10-20 23:13
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The guidance reaches operators using the self\-hosted 6\.0\.x\-to\-6\.1 online upgrade path\. No blocking findings; the documentation omission causes a failed upgrade attempt rather than an unsafe deployment. Findings 1. **Copied digest lacks required prefix** Reviews (1) · Last reviewed commit: "docs(self-hosting): pin Cube by digest o..." · Reviewed by the reviewer
#9658
2026-10-20 23:13
greptile
inline · P2
docs/self-hosting/configuration/online-upgrade.mdx:79docs
**Copied digest lacks required prefix** The instructions say to reuse the Cube digest above for `cube.image.digest`, but that example explicitly omits `sha256:`. Readers who copy the shown hex into their 6.1 values get `Online mode requires pinned Hub and Cube digests` during Helm rendering, before preparation can run. With otherwise-identical values, adding only `sha256:` makes rendering succeed. Show the complete target value: `cube.image.digest: "sha256:3859ec17f4e81fe5147cde8b6b81bccbed329023b803e4a315f0988e5c9372b8"`. This is a non-blocking documentation correction that prevents a failed upgrade attempt. ```suggestion Then put the same digests in the 6.1 online values (`hub.image.dig
… the rest (1501 characters)est`, `cube.image.digest`), including the `sha256:` prefix on both. Set `cube.image.digest` to `"sha256:3859ec17f4e81fe5147cde8b6b81bccbed329023b803e4a315f0988e5c9372b8"`. The chart adds Hub and Cube to the dependency inventory from them, so they need no `upgrade.online.dependencies` entry. The 6.1 chart defaults Cube to 1.7.50-1 with no digest, so this explicit digest keeps Cube on 1.7.47-1. ``` Artifacts **Complete executable Helm validation source** - The full Python harness downloads and checksum-verifies Helm, extracts the documented digest, renders both input forms, and asserts their observed behavior. **Helm download and checksum verification** - Captured download, checksum, extraction, and version commands all exited successfully, establishing that real Helm v3.17.3 was available. **Helm rejection with the copied bare Cube digest** - The real Helm upgrade-mode render used the bare hex from the documentation and exited 1 at the pinned Hub and Cube digest guard, before emitting manifests. **Successful Helm render with the prefixed Cube digest** - The same Helm render added only sha256: to the Cube digest and exited 0, emitting the complete manifests including the preparation hook. **Validation commands, settings, and successful assertions** - The captured harness run records the exact head, documentation excerpts, complete shared values, both commands and exit statuses, and passing preparation-hook and Cube-image assertions. Ran code and verified through TREX
#9615
2026-10-20 23:14
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The billing catalog now selects among multiple active Stripe response prices. It prioritizes graduated prices, explicit response tags, and newer creation timestamps, and applies meter filtering. It logs ignored candidates and still rejects plans with no response price. Pro and Scale monthly entries and yearly entries now share each plan’s resolved monthly response price. Tests cover selection, logging, meter handling, and missing or volume-only prices. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to cad96 The catalog change is mergeable after normal checks; no actionable pricing or checkout defect was establ
… the rest (6953 characters)ished. Security architecture risk: 🔵 Low · up to cad96 Price selection remains server-controlled, and billing authorization is unchanged. The main residual risk is ambiguous Stripe catalog configuration: without a tagged response meter, ranking can select a price without confirming its meter identity. No introduced authorization or tenant-isolation weakness was established. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — A catalog selection can affect organizations using the corresponding Pro or Scale price through checkout or subscription changes. Exposure follows the shared Stripe catalog rather than an organization-specific price override; ordinary request input does not provide price-selection authority. **Trust Boundaries and Controls** - _observed_ — The checkout action retains authentication, organization.manage_billing authorization, validated plan and interval inputs, and organization-owned customer lookup. Session creation uses catalog-derived line items and binds the session to the organization through its client reference. **Resilience and Maintainability Implications** - _observed_ — The existing checkout guard rejects mixed recurring intervals before creating a session. Existing webhook processing resolves organization ownership, returns retryable failures, and skips duplicate or older billing events. These lifecycle controls are not replaced by the new price resolver. **Hardening Proposals** - _proposed_ — If an authoritative response-meter identity is available operationally, validate candidates against it before ranking. This would strengthen billing identity assurance in the no-tagged-meter fallback without treating duplicate prices alone as a catalog outage. Pre-merge checks: 6 · 1 Failed checks: 1 warning | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Coverage Accounts For The Whole Diff | | The Coverage table does not account for the whole diff. The mutation row for foreign-meter filtering says only “Replace the meter filter with `matches`”; it does not name the required `file:line` (`st… | Update the Coverage table or Open gaps. Add `stripe-billing-catalog.ts:290` to the meter-filter mutation row. Add verification rows with named tests and observed outcomes for tagged-versus-untagged graduated ranking, warning/error logging f… | Passed checks: 6 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits syntax, uses the valid `fix` type and `billing` scope, describes the duplicate Stripe response-price resolution, and has a lowercase summary without a trailing p… | | Description check | | The description includes the required ticket reference, change summary, implementation links, breaking-change assessment, migration and environment notes, test command, coverage table, open gaps, risk… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description has an unticked breaking-change checkbox. The authoritative diff changes only `apps/web/modules/ee/billing/lib/stripe-billing-catalog.ts` and its test file. It changes internal Stri… | | Only The Ticket Line Carries A Magic Word | | The only magic-word pair in the PR description is the ticket line `Fixes ENG-1971` at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-<number>` or `#<number>` referen… | Full details: Title check **Explanation** The title follows Conventional Commits syntax, uses the valid `fix` type and `billing` scope, describes the duplicate Stripe response-price resolution, and has a lowercase summary without a trailing period. Full details: Description check **Explanation** The description includes the required ticket reference, change summary, implementation links, breaking-change assessment, migration and environment notes, test command, coverage table, open gaps, risks, and AI disclosure. It is complete and directly related to the changes, although the appended the reviewer-generated section is unnecessary. Full details: Breaking Changes Match The Diff **Explanation** The PR description has an unticked breaking-change checkbox. The authoritative diff changes only `apps/web/modules/ee/billing/lib/stripe-billing-catalog.ts` and its test file. It changes internal Stripe catalog price selection and logging; it does not change an endpoint, request/response shape, environment or config key, database schema, or the public SDK surfaces listed by this check. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The only magic-word pair in the PR description is the ticket line `Fixes ENG-1971` at the top. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table does not account for the whole diff. The mutation row for foreign-meter filtering says only “Replace the meter filter with `matches`”; it does not name the required `file:line` (`stripe-billing-catalog.ts:290`). The diff also changes explicit-tag precedence when both candidates are graduated, duplicate warning/error logging, and Scale response-price resolution, but the table lists only the Pro selection cases and does not cover these behaviours or list them under Open gaps. **Resolution** Update the Coverage table or Open gaps. Add `stripe-billing-catalog.ts:290` to the meter-filter mutation row. Add verification rows with named tests and observed outcomes for tagged-versus-untagged graduated ranking, warning/error logging for excluded and ignored prices, and the Scale response resolver wiring (including shared monthly price use for yearly entries). Comment `@the reviewer help` to get the list of available commands.
#9615
2026-10-20 23:20
cubic
inline · P1
apps/web/modules/ee/billing/lib/stripe-billing-catalog.ts:281defect-functional
When active tagged responses prices reference different meters, this allowlist admits untagged prices on every tagged meter; a graduated price on a stale meter can then win and be attached to checkout. Restrict untagged candidates to one resolved responses meter, or exclude them when tagged meters disagree.
#9615
2026-10-20 23:20
cubic
review-body
—summary
**1 issue found** across 2 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9615
2026-10-20 23:22
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Critical impact]** The change reaches paid\-plan billing and checkout for Pro and Scale\. Safe to merge with one non-blocking improvement: give tied responses prices an input-order-independent winner. Findings 1. **Tied prices depend on order** Reviews (1) · Last reviewed commit: "fix(billing): resolve duplicate Stripe r..." · Reviewed by the reviewer
#9615
2026-10-20 23:22
greptile
inline · P2
apps/web/modules/ee/billing/lib/stripe-billing-catalog.ts:302defect-functional
**Tied prices depend on order** The comparator leaves equally ranked prices tied when they have the same `created` timestamp. If Stripe returns those candidates in a different order, the selected billing price changes. Reversing two otherwise identical candidates changed the selected price ID in both the catalog and checkout items for both billing intervals. This is a non-blocking determinism concern; it does not establish a difference in charges. Add a final tie-break using `id` and a test that expects the same winner after reversing the candidates. ```suggestion return aMode - bMode || aTag - bTag || b.created - a.created || a.id.localeCompare(b.id); ``` Note: If this suggestion doe
… the rest (1009 characters)sn't match your team's coding style, reply to this and let me know. I'll remember it for next time! Artifacts **Executable tied-price catalog test** - This exact test was executed colocated with the catalog against both revisions, exercising reversed Stripe lists and checking the resulting catalog and checkout price IDs. **Reproducible before-and-after runner** - The executed runner supplies synthetic environment values, runs the same test against base and head, captures outputs and source, and restores temporary changes. **Base catalog rejects tied candidates in both orders** - The base-revision test captured duplicate-price errors for both list orders, showing that neither candidate was selected before the change. **PR catalog selects different IDs when list order reverses** - The head-revision test captured price\_pro\_tied\_a followed by price\_pro\_tied\_b after reversal, confirming order-dependent selection in catalog and checkout-item outputs. Ran code and verified through TREX
#9672
2026-10-20 23:23
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough File upload handling now tracks active uploads and the latest response value. Block and survey response updates merge changed answers without using stale render-time data. A Playwright regression test delays an upload, makes additional upload and text changes, then checks the visible answers and persisted response. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to 86c43 This change keeps overlapping file uploads from overwriting other answers, and it adds a regression test. No merge-blocking issue was found. Submitting while an upload is still running behaves as it did before this change. Security architecture
… the rest (5956 characters)risk: ⚪ Minimal · up to 86c43 The change preserves newer answers without expanding upload permissions or changing server-side ownership checks. No material security risk introduced or worsened by this PR was identified in the reviewed flow. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The changed state flow affects answers within a respondent's survey instance. Preserving additional accepted completions does not establish broader tenant or storage authority: response creation and update still validate private file URLs against workspace, survey, and upload-element ownership. **Trust Boundaries and Controls** - _observed_ — The client file count is not the storage authorization boundary. Existing server controls apply workspace rate limiting and signed-upload content-length restrictions independently of that count. These controls do not establish whether a separate aggregate storage quota exists. **Resilience and Maintainability Implications** - _observed_ — Reservations begin after asynchronous validation, and first rejection releases a batch's count even if sibling uploads remain active. The base already admitted the corresponding requests and cleared its indicator on first rejection. This leaves imperfect client accounting, but does not demonstrate a newly weakened server control or failure-containment boundary. Pre-merge checks: 6 · 1 Failed checks: 1 warning | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Coverage Accounts For The Whole Diff | | The Coverage table is populated and covers the upload, text, saved-response, file-limit, indicator, and regression-flow behaviors. The Open gaps subsection is present. However, the diff changes render… | Attach a screenshot or screencast to the PR description that shows the changed rendered UI, such as overlapping uploads with the preserved files and the active “Uploading…” indicator. | Passed checks: 6 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title uses the required Conventional Commits format, uses a valid type and scope, describes the upload fix, uses lower case, and includes a valid ENG-3945 reference. | | Description check | | The description includes the required sections, explains the behavior change, identifies relevant code, documents breaking changes and migrations, and provides test coverage, gaps, and risks. It is mo… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves the breaking-change checkbox unticked and states `None`. The diff changes internal survey state merging and adds an end-to-end test. It does not change request or response fi… | | Only The Ticket Line Carries A Magic Word | | The description contains one magic-word pair: `Ref ENG-3945` on the top ticket line. No other `Fixes`, `Closes`, `Resolves`, or `Ref` appears next to an `ENG-<number>` or `#<number>` reference. | Full details: Description check **Explanation** The description includes the required sections, explains the behavior change, identifies relevant code, documents breaking changes and migrations, and provides test coverage, gaps, and risks. It is mostly complete, although the ticket line uses Ref even though the description says the PR closes the ticket. Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves the breaking-change checkbox unticked and states `None`. The diff changes internal survey state merging and adds an end-to-end test. It does not change request or response fields, field types, requiredness, routes, configuration, migrations, or public SDK signatures. `Survey`, `BlockConditional`, and `FileUploadElement` declarations remain unchanged, and the public `packages/surveys/src/index.ts` entrypoint is not modified. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table is populated and covers the upload, text, saved-response, file-limit, indicator, and regression-flow behaviors. The Open gaps subsection is present. However, the diff changes rendered survey UI: uploaded files and the “Uploading…” indicator can now remain visible during overlapping uploads. The PR description attaches no screenshot or screencast, which is an explicit failure condition. **Resolution** Attach a screenshot or screencast to the PR description that shows the changed rendered UI, such as overlapping uploads with the preserved files and the active “Uploading…” indicator. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 4 - Waived: $1.00 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9672
2026-10-20 23:30
cubic
inline · P1
packages/surveys/src/components/elements/file-upload-element.tsx:209defect-concurrency
Both selections can pass this check before either is counted: `handleFileSelect` awaits `validateAndFilterFiles` before `uploadFiles` increments the ref. At 24 saved files, two one-file selections can therefore leave 26; recheck or reserve capacity after validation.
#9672
2026-10-20 23:30
cubic
inline · P2
packages/surveys/src/components/elements/file-upload-element.tsx:293defect-concurrency
This append also preserves duplicate uploads: `filterDuplicateFiles` only checks the rendered `value`, so selecting the same filename while its first upload is pending passes validation and both URLs are saved. Include in-flight filenames in duplicate filtering.
#9672
2026-10-20 23:30
cubic
inline · P2
packages/surveys/src/components/elements/file-upload-element.tsx:318defect-concurrency
`Promise.all` rejects when one file fails, even while sibling uploads continue, but this subtracts the whole batch and turns off the indicator. Decrement per file on settlement or wait for all siblings before releasing their slots.
#9672
2026-10-20 23:30
cubic
review-body
—summary
**3 issues found** across 4 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9672
2026-10-20 23:36
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The change reaches the survey\-answering flow and the responses people submit\. Not safe to merge until the confirmed upload-loss race is fixed. Findings 1. **Completed uploads can disappear** Reviews (1) · Last reviewed commit: "fix(surveys): keep every answer when fil..." · Reviewed by the reviewer
#9672
2026-10-20 23:36
greptile
inline · P1
packages/surveys/src/components/elements/file-upload-element.tsx:54defect-concurrency
**Completed uploads can disappear** When three overlapping uploads finish close together, this delayed effect can overwrite a newer file list. After A renders, B publishes `[A, B]`. Before the next render, Preact runs A's pending effect and resets `latestValueRef` to `[A]`. If C then finishes before the newer effect runs, it publishes `[A, C]`, dropping B from both the displayed files and the response answer. This sequence reproduced with Preact's default scheduler and must be fixed before merging. Synchronize incoming values without allowing a delayed effect to roll back newer upload completions, and cover this completion order with a regression test. Artifacts **Recording of the check*
… the rest (2295 characters)* - Selected three files and completed their controlled uploads at render boundaries using the default scheduler; the parent revision retains only C. **Recording of the check** - Repeated the three-file completion sequence using the default scheduler on PR55 head; the emitted and rendered answer ends at A and C, dropping B. **Final file list before PR55** - Captured the rendered parent-revision upload component after all three uploads completed; only C remains. **Final file list on PR55 head** - Captured the rendered head component and runtime trace after all three uploads completed; A and C remain while B is missing. **Command output from the check** - Ran the reproduction across both revisions and both scheduling configurations with exit code 0; all asserted emitted sequences matched the observed loss. **Evidence from the check** - Builds exact Git component sources, launches Chromium, controls upload completions, asserts results, and records evidence; production sources remain unchanged. **Evidence from the check** - Mounts the actual upload component and observes Preact render boundaries while resolving storage promises; the trace exposes the ref rollback. **Evidence from the check** - Extracted the parent revision’s component directly from Git for execution; this is the source used for the before comparison. **Evidence from the check** - Extracted the requested head component directly from Git for execution; the production component was not patched. **Command output from the check** - Executed both controlled-effect cases before recording failed because Playwright’s expected FFmpeg executable was absent; the component race had already reproduced. **Command output from the check** - Executed the component reproduction using the supplied recorder binary before video finalization failed; no video file was produced by that attempt. **Command output from the check** - Retried Playwright video finalization and captured its missing-file error; recorder compatibility still prevented video output. **Command output from the check** - Ran with Playwright browser debugging and observed that the supplied FFmpeg lacked VP8; switching only the capture tooling to system FFmpeg enabled the final recordings. Ran code and verified through TREX
#9620
2026-10-20 23:38
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough Sign-in auditing moved from a session database hook to a Better Auth post-hook plugin that classifies completed sign-ins and records audit and login-tracking effects independently. The plugin and OAuth processing now run after two-factor processing. Tests cover two-factor setup and completion, SSO callbacks, and MCP OAuth authorization in navigation and fetch modes. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to ae9f1 This change records sign-ins and lets pending MCP authorizations continue only after the second factor is completed. A password alone no longer yields a session or an OAuth authorization code
… the rest (7791 characters)for users with 2FA enabled. Session refreshes and 2FA enrollment are no longer counted as sign-ins. Integration tests cover these flows, and no blocking issues remain. Security architecture risk: 🔵 Low · up to ae9f1 The change strengthens the second-factor boundary by delaying authorization and sign-in recording until authentication completes. No newly introduced security bypass was established in the reviewed flows. Concurrent requests, interruption recovery, and background audit delivery were not validated end to end. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The changed ordering affects the shared web authentication pipeline: user-session completion, user-level sign-in records, and pending MCP authorizations. The inspected MCP tests exercise a previously consented client with survey-read and offline-access scopes; they do not establish coverage of every client or scope combination. **Trust Boundaries and Controls** - _observed_ — The regression tests assert that a correct password plus a pending OAuth query yields a second-factor challenge, no authorization code, and no persisted session. After TOTP completion, they assert one code that exchanges for tokens whose subject is the completing user, in both navigation and fetch modes. These are inspected assertions, not independently executed results. - _observed_ — The inspected two-factor producer verifies the factor before consuming the signed challenge and requires successful consumption before creating a session. Replayed or losing concurrent requests cannot proceed through that session-creation branch without a consumed challenge. Backup-code verification also uses a conditional update before session completion. **Resilience and Maintainability Implications** - _inferred_ — Single-use challenge consumption supplies upstream duplicate-completion protection, but consumption precedes session creation and is not transactionally coupled to audit persistence. Interruption can therefore require authentication restart or leave observability incomplete. The reviewed comparison does not establish that this PR introduces or worsens those existing recovery limits. Pre-merge checks: 5 · 2 Failed checks: 2 warnings | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Breaking Changes Match The Diff | | The breaking-changes checkbox is unticked, but the diff changes an existing response status. In `apps/web/modules/auth/lib/auth.ts:455`, `oauthProvider()` now runs after `twoFactor()`. For a pending M… | Tick `- [x] This PR contains breaking changes` and document the status change for password-only `/api/auth/sign-in/email` requests that resume a pending MCP authorization. | | Coverage Accounts For The Whole Diff | | The Coverage table has rows with verification methods but no observed outcomes. The `Password step records nothing; the second factor records one signedIn` row names `auth-two-factor.integration.test.… | Add observed outcomes to both rows. State that the two-factor integration test passed with zero audits and null `lastLoginAt` after the password step, then one successful `signedIn` audit and a non-null `lastLoginAt` after TOTP. State that … | Passed checks: 5 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows the Conventional Commits format, uses the valid fix type and auth scope, describes the sign-in and MCP authorization change, and includes Linear ticket references. | | Description check | | The description covers the required ticket reference, behavior change, affected areas, breaking changes, migrations, testing coverage, open gaps, and risks. It is detailed and consistent with the chan… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Only The Ticket Line Carries A Magic Word | | PASS. The description contains one magic-word occurrence: `Ref ENG-3271, ENG-3933` on the top ticket line. No other `Fixes`, `Closes`, `Resolves`, or `Ref` occurrence is paired with an `ENG-<number>` … | Full details: Description check **Explanation** The description covers the required ticket reference, behavior change, affected areas, breaking changes, migrations, testing coverage, open gaps, and risks. It is detailed and consistent with the changeset. Full details: Only The Ticket Line Carries A Magic Word **Explanation** PASS. The description contains one magic-word occurrence: `Ref ENG-3271, ENG-3933` on the top ticket line. No other `Fixes`, `Closes`, `Resolves`, or `Ref` occurrence is paired with an `ENG-&lt;number&gt;` or `#&lt;number&gt;` reference. Full details: Breaking Changes Match The Diff **Explanation** The breaking-changes checkbox is unticked, but the diff changes an existing response status. In `apps/web/modules/auth/lib/auth.ts:455`, `oauthProvider()` now runs after `twoFactor()`. For a pending MCP authorization, the password-only navigation previously returned the OAuth redirect with the authorization code; the new integration test requires `200` with no `Location` at `apps/web/modules/auth/lib/mcp-oauth-two-factor.integration.test.ts:152-154`. **Resolution** Tick `- [x] This PR contains breaking changes` and document the status change for password-only `/api/auth/sign-in/email` requests that resume a pending MCP authorization. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table has rows with verification methods but no observed outcomes. The `Password step records nothing; the second factor records one signedIn` row names `auth-two-factor.integration.test.ts` but does not state what the test observed. The `SSO redirect callback recorded; refreshes and re-issues not` row similarly names tests but gives no result. The diff adds these behaviors through `signInAuditPlugin` and `isNewSignIn` in `auth.ts` and `better-auth-observability.ts`, so the missing outcomes violate the check. **Resolution** Add observed outcomes to both rows. State that the two-factor integration test passed with zero audits and null `lastLoginAt` after the password step, then one successful `signedIn` audit and a non-null `lastLoginAt` after TOTP. State that the SSO callback test passed with a redirect/session and audit, while refresh and re-issue cases returned false or emitted no audit. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 8 - Waived: $2.00 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9620
2026-10-20 23:45
cubic
review-body
—summary
**No issues found** across 8 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9620
2026-10-20 23:48
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No merge-blocking issues were found; the focused authentication checks support merging this change. Reviews (1) · Last reviewed commit: "fix(auth): record a sign-in, and resume ..." · Reviewed by the reviewer
#9652
2026-10-20 23:50
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The installer now generates Traefik configuration with encoded-path restrictions and authentication routers that limit request bodies to 1 MiB. The Traefik image is pinned to v3.6.25. Added tests and a Docker Compose smoke test check configuration, routing, redirects, body limits, and HSTS behavior. A GitHub Actions job runs the smoke test. The self-hosting guides document the configuration and setup requirements. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to 99c7f No actionable issue remains identified in the one-click Traefik auth-limit change; it is ready for normal merge checks. Security architecture
… the rest (7321 characters)risk: 🔵 Low · up to 99c7f The change adds narrowly scoped ingress protection without changing authentication privileges or stored data. Direct application access remains outside that protection. Downstream body-size enforcement and live migration recovery are not fully verified. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — An anonymous sender can exercise the new buffering policy without application privileges. Its immediate resource boundary is the deployment's shared Traefik ingress and downstream authentication service, not a newly granted tenant or data-store capability. Host-network restrictions determine whether the unchanged direct application port is externally reachable. **Security Findings and Attack Paths** - _inferred_ — Where port 3000 is reachable, a caller can avoid the 1 MiB Traefik ceiling by contacting Formbricks directly. This path predates the PR and is explicitly documented, so it is a limitation of the protection rather than an introduced architecture concern. **Trust Boundaries and Controls** - _observed_ — The generated controls combine auth-router priority 1000, explicit service selection, request buffering, and encoded-path restrictions on both entrypoints. When HSTS is enabled, HTTP redirects to HTTPS; otherwise a separate HTTP authentication router receives the same body-limit middleware. **Resilience and Maintainability Implications** - _inferred_ — Buffering moves slow-upload occupancy from the application to Traefik. The configured 60-second read timeout bounds individual request reading, but the per-request byte ceiling is not an aggregate concurrency or memory budget. Partial manual migration can leave protection incomplete until configuration and labels are both active; live interruption and recovery behavior were not verified. **Hardening Proposals** - _proposed_ — Where the edge ceiling is intended as a deployment-wide guarantee, restrict direct access to port 3000 to trusted ingress. Treat this as hardening of an existing deployment boundary, not remediation of a newly introduced exposure. - _proposed_ — Clarify that custom-SSL edits must preserve the generated authentication labels, and add migration validation and recovery instructions for the paired static configuration and middleware changes. The current custom-SSL snippet omits those labels despite instructing readers to retain the remaining one-click configuration. Pre-merge checks: 6 · 1 Failed checks: 1 warning | Check name | Status | Explanation | Resolution | | :----------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Breaking Changes Match The Diff | | The breaking-changes checkbox is unticked, but the diff changes the HTTP status for an existing `/api/auth/*` request case. `docker/formbricks.sh:490` adds a Traefik buffering limit of 1 MiB, and `doc… | Update the PR description: change the checkbox to `- [x] This PR contains breaking changes` and state that one-click Traefik now returns HTTP 413 for `/api/auth/*` request bodies over 1 MiB. | Passed checks: 6 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits, uses the valid `fix(docker)` type and scope, describes the request-body cap, remains lowercase, has no trailing period, and includes the ENG-3977 reference. | | Description check | | The description includes all required sections, explains the change and affected installations, links to relevant code, documents breaking-change status, migrations, test coverage, open gaps, and risk… | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Only The Ticket Line Carries A Magic Word | | The description contains only the allowed ticket-line pair: “Fixes ENG-3977” at the top. Other ticket and issue references, such as ENG-3248, `#9636`, and traefik#11568, do not appear next to Fixes, Clo… | | Coverage Accounts For The Whole Diff | | The description has a non-empty Coverage table with six verification entries, named unit/e2e tests, rerun commands, mutation locations, and observed outcomes. The rows and folded smoke-test results co… | Full details: Description check **Explanation** The description includes all required sections, explains the change and affected installations, links to relevant code, documents breaking-change status, migrations, test coverage, open gaps, and risks. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The description contains only the allowed ticket-line pair: “Fixes ENG-3977” at the top. Other ticket and issue references, such as ENG-3248, `#9636`, and traefik#11568, do not appear next to Fixes, Closes, Resolves, or Ref. Full details: Coverage Accounts For The Whole Diff **Explanation** The description has a non-empty Coverage table with six verification entries, named unit/e2e tests, rerun commands, mutation locations, and observed outcomes. The rows and folded smoke-test results cover the changed Traefik version, static configuration, auth buffering and limits, router priority, redirects, encoded paths, HSTS/HTTPS combinations, and uncapped non-auth routes. Open gaps is present and lists the VM-install and direct-port limitations. The diff changes no product UI. Full details: Breaking Changes Match The Diff **Explanation** The breaking-changes checkbox is unticked, but the diff changes the HTTP status for an existing `/api/auth/*` request case. `docker/formbricks.sh:490` adds a Traefik buffering limit of 1 MiB, and `docs/self-hosting/setup/one-click.mdx:489` states that larger requests now return `413 Request Entity Too Large` instead of being forwarded. Please tick `- [x] This PR contains breaking changes` and document this status change. **Resolution** Update the PR description: change the checkbox to `- [x] This PR contains breaking changes` and state that one-click Traefik now returns HTTP 413 for `/api/auth/*` request bodies over 1 MiB. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 6 - Waived: $1.50 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9652
2026-10-20 23:56
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The change reaches authentication traffic on new one\-click installs that use Traefik\. No blocking issue was established. The custom-SSL documentation concern is non-blocking. Findings 1. **Custom SSL drops the cap** Reviews (1) · Last reviewed commit: "fix(docker): cap /api/auth request bodie..." · Reviewed by the reviewer
#9652
2026-10-20 23:56
greptile
inline · P2
docs/self-hosting/configuration/custom-ssl.mdx:51docs
**Custom SSL drops the cap** The updated guide describes preserving the authentication body cap, but Step 7’s complete `labels` example omits `auth-body-limit` and `formbricks-auth`. If readers replace their installed labels with that example, HTTPS continues working but the 1 MiB authentication cap disappears. This is a non-blocking documentation issue: oversized authentication requests reach the application-facing service instead of being rejected by Traefik. Include the authentication labels in the example, or explicitly instruct readers to retain them while changing certificate settings. Artifacts **Runnable custom SSL labels replacement test** - The authored Python harness extracts
… the rest (871 characters)repository configurations, executes installer label generation, starts disposable services, and asserts before-and-after behavior, making the comparison reproducible. **Guide instructions and installer label evidence** - Executed source inspection captures the exact head, changed warning, complete labels example, and installer cap configuration, showing the documentation inconsistency. **HTTPS requests with installer labels retained** - Live requests through Traefik with installer-generated labels reject oversized auth bodies with HTTP 413 before backend delivery, showing the installed cap works. **HTTPS requests after documented labels replacement** - The same live requests using Step 7’s labels return HTTP 200 with exact oversized byte counts at the backend while TLS remains valid, showing replacement drops the cap. Ran code and verified through TREX
#9652
2026-10-20 23:59
cubic
inline · P2
docs/self-hosting/configuration/custom-ssl.mdx:51docs
Include the `auth-body-limit` middleware and `formbricks-auth` router in the complete labels example, or explicitly tell readers to preserve them when changing certificate settings; replacing the labels as shown removes the 1 MiB edge cap.
#9652
2026-10-20 23:59
cubic
review-body
—summary
**1 issue found** across 6 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9613
2026-10-21 00:00
coderabbit
issue-comment
—summary
### Walkthrough Workspace creation now accepts a required limit option and checks finite limits inside the creation transaction under an organization-row lock. The workspace action applies rate limiting, retrieves the limit and current count concurrently, and passes the limit to creation. Organization setup paths pass a null limit for default workspaces. Unit and Postgres integration tests cover limit enforcement, concurrent creation, and action behavior. Priority: ⬇️ Low Merge Risk: 🔵 Low · up to 943c1 Workspace-limit enforcement is covered by database-backed tests. The unit test may fail after an equivalent implementation change, but this bounded cleanup need not block merging. Secur
… the rest (6262 characters)ity architecture risk: 🔵 Low · up to 943c1 The design strengthens workspace-limit enforcement while preserving organization authorization. No introduced security weakness was confirmed. Remaining uncertainty concerns production database isolation and subscription limits changing during creation. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _inferred_ — The analyzed external attack path requires an authenticated user authorized to manage the target organization. Its directly affected resource is that organization's workspace capacity. The lock is keyed to the organization rather than a global workspace counter. **Security Findings and Attack Paths** - _inferred_ — The prior caller-only check allowed concurrent authorized creates to pass against the same count. The new protocol addresses that path by serializing finite-limit creation for the organization before recounting. This guarantee depends on the documented READ COMMITTED visibility assumption and the supplied limit snapshot, not synchronization with concurrent entitlement changes. **Trust Boundaries and Controls** - _observed_ — The quota option remains a trusted internal-caller responsibility. Inspected signup, organization-creation, and SSO bootstrap callers pass null immediately after creating an organization; the existing-organization action obtains its limit server-side. The service does not independently prove bootstrap provenance. - _observed_ — Quota rejection propagates out of the creation transaction. OperationNotAllowedError is an expected error translated into a server-action error message, not successful workspace data. Its statusCode property alone does not establish an HTTP 403 transport response. **Resilience and Maintainability Implications** - _inferred_ — Failure within the creation transaction rolls back workspace and team-link writes, preserving quota accounting on rejected or interrupted attempts. Authorization projection remains outside that transaction under the pre-existing best-effort boundary; a projection failure does not undo the committed workspace. The PR does not introduce that split. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title follows Conventional Commits syntax, uses a valid type and scope, describes the workspace-limit transaction change, uses lowercase summary text without a trailing period, and includes a vali… | | Description check | | The description includes the required ticket reference, change summary, review locations, breaking-change declaration, migration and environment status, test commands, coverage, open gaps, risks, and … | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description has an unticked Breaking changes checkbox. The diff adds no listed breaking change. It changes the internal `createWorkspace` signature in `apps/web/modules/workspaces/settings/lib/… | | Only The Ticket Line Carries A Magic Word | | The description contains one magic-word pair: `Fixes ENG-3921` on the top ticket line. No other `Fixes`, `Closes`, `Resolves`, or `Ref` pair appears next to an `ENG-<number>` or `#<number>` reference. | | Coverage Accounts For The Whole Diff | | The description accounts for the changed behavior. The Coverage table names the concurrent limit behavior, lock/count/insert ordering, missing-organization rejection, rate-limit and early-rejection be… | Full details: Title check **Explanation** The title follows Conventional Commits syntax, uses a valid type and scope, describes the workspace-limit transaction change, uses lowercase summary text without a trailing period, and includes a valid ENG-3921 reference. Full details: Description check **Explanation** The description includes the required ticket reference, change summary, review locations, breaking-change declaration, migration and environment status, test commands, coverage, open gaps, risks, and AI note. It is more verbose than the template budget and includes extra generated content, but the required information is present and relevant. Full details: Breaking Changes Match The Diff **Explanation** The PR description has an unticked Breaking changes checkbox. The diff adds no listed breaking change. It changes the internal `createWorkspace` signature in `apps/web/modules/workspaces/settings/lib/workspace.ts:349`, but internal functions and workspace-module exports are explicitly excluded. No SDK signature, route, webhook payload, HTTP status, environment/config key, migration, or database schema change appears in the changed files. Full details: Coverage Accounts For The Whole Diff **Explanation** The description accounts for the changed behavior. The Coverage table names the concurrent limit behavior, lock/count/insert ordering, missing-organization rejection, rate-limit and early-rejection behavior, unlimited and new-organization guards, and real-action behavior. Each row gives a method and observed outcome. The unit rows name test files, the red-on-main rows have the Rerun command, and the mutation rows name reviewer-editable locations. Open gaps is present and gives reasons for missing live Stripe coverage and unit-only new-organization coverage. The diff changes server behavior and tests, but it does not change rendered UI. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 10 - Waived: $2.50 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9613
2026-10-21 00:05
coderabbit
inline · P3
apps/web/modules/workspaces/settings/lib/workspace.test.ts:689tests
**📐 Maintainability & Code Quality** | **🔵 Trivial** | **⚡ Quick win** **Assert the outcome here, not the SQL text and call order.** This test couples the unit suite to the raw Prisma SQL and mock invocation order. Those assertions can fail after a behavior-preserving lock API or query rewrite. They cannot reproduce the concurrent database behavior. The real-Postgres integration tests cover the workspace-limit invariant. Keep the successful result and organization-scoped count assertion, and remove the SQL and ordering assertions. ♻️ Proposed reduction ```diff await expect(createWorkspace("org1", { name: "Workspace 3" }, { workspaceLimit: 3 })).resolves.toEqual( baseWo
… the rest (809 characters)rkspace ); - - const { sql, values } = lockQuery(); - expect(sql).toMatch(/FROM "Organization"\s+WHERE "id" = \?\s+FOR NO KEY UPDATE/); - expect(values).toEqual(["org1"]); expect(prisma.workspace.count).toHaveBeenCalledWith({ where: { organizationId: "org1" } }); - - const [lockOrder] = vi.mocked(prisma.$queryRaw).mock.invocationCallOrder; - const [countOrder] = vi.mocked(prisma.workspace.count).mock.invocationCallOrder; - const [createOrder] = vi.mocked(prisma.workspace.create).mock.invocationCallOrder; - const [transactionOrder] = vi.mocked(prisma.$transaction).mock.invocationCallOrder; - expect(transactionOrder).toBeLessThan(lockOrder); - expect(lockOrder).toBeLessThan(countOrder); - expect(countOrder).toBeLessThan(createOrder); ```
#9613
2026-10-21 00:05
coderabbit
review-body
—summary
**Actionable comments posted: 1**
#9613
2026-10-21 00:08
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** No blocking issues were found; the validated workspace-cap change is safe to merge. Reviews (1) · Last reviewed commit: "fix(workspaces): enforce the workspace l..." · Reviewed by the reviewer
#9613
2026-10-21 00:11
cubic
inline · P1
apps/web/modules/organization/actions.ts:60defect-concurrency
Owner membership precedes this insert, so a concurrent client can discover the organization and pass the locked recount while this null-limit insert is uncommitted. Cloud organizations default to one workspace; enforce this starter insert under the cap lock.
#9613
2026-10-21 00:11
cubic
review-body
—summary
**1 issue found** across 10 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9635
2026-10-21 00:13
coderabbit
issue-comment
—summary
### Walkthrough The signup email-domain check now blocks subdomains of designated wildcard disposable providers. It normalizes domains and checks parent domains up to the maximum wildcard label depth. Other disposable and personal domains remain exact-match only. Tests cover wildcard roots and nested subdomains, lookalike domains, long-domain lookup bounds, and exact-only matching. Priority: ⬇️ Low Merge Risk: 🟡 Moderate · up to 4007c Cloud signup and SSO provisioning now reject any address beneath a listed wildcard provider. This correctly blocks 33mail.com and similar disposable services. It also blocks legitimate organizations whose domains sit under shared namespaces such as web.id
… the rest (6113 characters)or my.id. Excluding those namespaces from wildcard matching before merge would avoid turning away real customers. Security architecture risk: 🔵 Low · up to 4007c The change tightens Cloud signup policy without introducing a new access path. Self-hosted behavior and validated-invite exceptions are preserved. No introduced security defect was identified, although live signup and SSO requests were not exercised. **Retained concerns** No architecture-level concerns identified. Security review details **Security Blast Radius** - _observed_ — The effective policy change spans new Cloud credential accounts and just-in-time SSO accounts using the shared gate. It is not confined to one organization, but does not alter role selection or grant additional authority. Self-hosted requests bypass this domain policy. **Security Findings and Attack Paths** - _inferred_ — The change closes the described exact-match evasion in which a disposable-provider subdomain reaches account creation. The checked native credential route also reaches the shared policy before insertion. No introduced bypass was identified in the inspected paths; this conclusion is based on source wiring rather than live endpoint execution. **Trust Boundaries and Controls** - _observed_ — A caller-controlled email or SSO email claim does not itself establish the invite exemption. The shared invite resolver verifies the token, binds its email case-insensitively to the registering address and checks invite validity. Verification failure does not produce a valid exemption. **Resilience and Maintainability Implications** - _inferred_ — Rejected wildcard-domain requests stop before creation and do not acquire a reusable admission marker. Allowed or invite-exempt requests retain the pre-existing separation between account creation and later membership work, where partial failures can leave an account awaiting reconciliation. That lifecycle limitation predates this PR and does not turn a rejected request into an admitted one. Pre-merge checks: 6 · 1 Failed checks: 1 warning | Check name | Status | Explanation | Resolution | | :---------------------------------- | :----------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Coverage Accounts For The Whole Diff | | The Coverage table covers the wildcard exact-match, subdomain, lookalike, and bounded-lookup behaviors. It also includes an Open gaps entry for the untested live signup form. However, the `unit (mutat… | Update the mutation row to name `apps/web/modules/auth/lib/signup-email-domain.ts:75` as the mutation location. Keep the existing rerun command and observed outcome. | Passed checks: 6 passed | Check name | Status | Explanation | | :--------------------------------------- | :--------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Description check | | The description covers the required sections, explains the behavior change and scope, documents testing and coverage, and identifies open gaps and risks. It also explains why no ticket applies. | | Title check | | The title follows Conventional Commits syntax, uses the allowed `fix` type and `auth` scope, stays lower case without a period, and clearly describes the main change. | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves `- [ ] This PR contains breaking changes` unticked. The authoritative diff changes only the internal signup-domain predicate and its tests. It adds wildcard-domain matching, … | | Only The Ticket Line Carries A Magic Word | | The supplied PR description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with an `ENG-<number>` or `#<number>`. The `#L35-L54` URL fragment is not next to a magic word. The `#9635` refer… | Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves `- [ ] This PR contains breaking changes` unticked. The authoritative diff changes only the internal signup-domain predicate and its tests. It adds wildcard-domain matching, but it does not change an API shape, endpoint, webhook payload, HTTP status implementation, environment/config key, database schema, or public SDK signature. The custom check treats internal modules and functions as non-breaking. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The supplied PR description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` paired with an `ENG-&lt;number&gt;` or `#&lt;number&gt;`. The `#L35-L54` URL fragment is not next to a magic word. The `#9635` reference appears in PR metadata, not in the description. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table covers the wildcard exact-match, subdomain, lookalike, and bounded-lookup behaviors. It also includes an Open gaps entry for the untested live signup form. However, the `unit (mutation)` row says to remove `|| isSubdomainOfWildcardDomain` without naming the mutated `file:line`, as required. The changed expression is at `apps/web/modules/auth/lib/signup-email-domain.ts:75`. **Resolution** Update the mutation row to name `apps/web/modules/auth/lib/signup-email-domain.ts:75` as the mutation location. Keep the existing rerun command and observed outcome. Comment `@the reviewer help` to get the list of available commands.
#9635
2026-10-21 00:14
cubic
inline · P2
apps/web/modules/auth/lib/signup-email-domain.test.ts:100tests
This does not test a wildcard root missing from the exact list: `anonaddy.com` is already in `PERSONAL_EMAIL_DOMAINS` and is asserted above. Use a root present only in `wildcard.json` to cover that behavior.
#9635
2026-10-21 00:14
cubic
inline · P2
apps/web/modules/auth/lib/signup-email-domain.ts:50defect-functional
This treats every child of shared namespaces such as `my.id`, `web.id`, and `ddns.net` as disposable, so legitimate custom-domain users can be rejected from Cloud signup and SSO. Exclude these broad namespaces from wildcard matching or narrow the entries to provider-controlled tenants.
#9635
2026-10-21 00:14
cubic
review-body
—summary
**2 issues found** across 2 files Reply with feedback, questions, or to request a fix.View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9635
2026-10-21 00:15
coderabbit
inline · P3
apps/web/modules/auth/lib/signup-email-domain.test.ts:113tests
**📐 Maintainability & Code Quality** | **🔵 Trivial** | **🏗️ Heavy lift** **Replace the prototype spy with a behavior-based performance check.** This assertion counts `Set.prototype.has` calls, not total work. If the matcher changes to another lookup method, the test can pass with zero counted calls even when it walks every label. Keep the long-domain output assertions. Check the work bound with an implementation-independent performance test. As per path instructions, “Assert on inputs and outputs. Flag tests that lock implementation details.” _Source: Path instructions_
#9635
2026-10-21 00:15
coderabbit
inline · P1
apps/web/modules/auth/lib/signup-email-domain.ts:27defect-functional
**🎯 Functional Correctness** | **🟠 Major** | **🏗️ Heavy lift** **Exclude shared registration namespaces from wildcard matching.** The imported list includes `my.id` and `web.id`, which are public suffixes. This set therefore treats an independently registered address such as `user@company.web.id` as disposable. For an uninvited Cloud signup, the changed predicate rejects that address. Filter shared namespaces from the wildcard set, and retain wildcard matching for providers such as `33mail.com`. (raw.githubusercontent.com)
#9635
2026-10-21 00:15
coderabbit
review-body
—summary
**Actionable comments posted: 2**
#9635
2026-10-21 00:19
greptile
issue-comment
—summary
Confidence Score: 4/5 **[Medium impact]** The change reaches account creation on Formbricks Cloud\. No blocking issue was established; the test-coverage improvement is recommended but does not prevent merging. Findings 1. **Root test misses regressions** Reviews (1) · Last reviewed commit: "fix(auth): block subdomains of wildcard ..." · Reviewed by the reviewer
#9635
2026-10-21 00:19
greptile
inline · P2
apps/web/modules/auth/lib/signup-email-domain.test.ts:100tests
**Root test misses regressions** The new wildcard-root test uses `anonaddy.com`, which is already in `PERSONAL_EMAIL_DOMAINS`. Removing `...wildcardBlockedEmailDomains` from `blockedEmailDomains` leaves all 17 existing tests passing, even though 10 wildcard-only roots then become allowed. This is a non-blocking coverage gap: the suite cannot catch that regression. Use a mocked wildcard-only domain, or verify that the chosen root is absent from both existing exact-match lists and has no shorter wildcard parent. Artifacts **Executable baseline and mutation harness** - Ran both suites before and after removing only the root spread, captured command output, and restored source in a finally b
… the rest (1083 characters)lock—this is the executed validation procedure. **Executed wildcard-root isolation test** - Executed this test alongside the real predicate to distinguish exact-list overlap and shorter-parent matches from root-spread behavior—the probe detects the missed regression. **Test output with original root spread** - Ran the existing suite and isolation probe against unmodified PR59 code—17 existing tests and the new root invariant all passed. **Test output with root spread temporarily removed** - Captured the one-line mutation and reran both suites—the existing tests stayed green while the probe detected 10 newly allowed roots with child blocking preserved. **Source lines and clean tracked-code restoration** - Ran git diff checks and numbered source inspection after restoring code—the relevant tracked files have no remaining changes. **Validation harness execution output** - Executed the Python harness and captured its exit status and phase results—the expected baseline-pass and mutation-failure sequence completed successfully. Ran code and verified through TREX
#9627
2026-10-21 00:20
coderabbit
issue-comment
—summary
No actionable comments were generated in the recent review. 🎉 ### Walkthrough The workspace survey-styling control moved from the main styling section to a separate Survey overrides section. Its loading view and localization were updated. The survey-level styling labels were revised across the editor, tests, and documentation. The summaries report no changes to CSS behavior. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to d87bd The PR changes styling-control placement and terminology while preserving the existing setting and styling behavior. Pre-merge checks: 7 | Check name | Status | Explanation | | :--------------------------------------- | :----------------------------------------
… the rest (3423 characters)----------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | | The title uses the required Conventional Commits format, uses a valid type and scope, stays lowercase, has no trailing period, and accurately describes the toggle wording change. | | Description check | | The description covers the user-visible change, implementation scope, testing, breaking-change status, migrations, risks, open gaps, and review locations. It is complete enough for review. | | Linked Issues check | | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | | Check skipped because no linked issues were found for this pull request. | | Breaking Changes Match The Diff | | The PR description leaves “This PR contains breaking changes” unticked. The reviewed diff changes UI placement, translation keys, comments, tests, documentation, and images. It keeps the `allowStyleOv… | | Only The Ticket Line Carries A Magic Word | | The supplied PR description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` followed by `ENG-<number>` or `#<number>`. The top line is `No ticket: ...` and contains no ticket pair. The `#L...` sou… | | Coverage Accounts For The Whole Diff | | The Coverage table is populated and covers the changed behaviors. The two e2e rows cover the workspace toggle rename, placement, save behavior, and survey-editor label; the Rerun line names `survey-st… | Full details: Breaking Changes Match The Diff **Explanation** The PR description leaves “This PR contains breaking changes” unticked. The reviewed diff changes UI placement, translation keys, comments, tests, documentation, and images. It keeps the `allowStyleOverwrite` form field and behavior. It does not change an API shape, endpoint, response status, environment/config key, database schema, or public SDK signature. Full details: Only The Ticket Line Carries A Magic Word **Explanation** The supplied PR description contains no `Fixes`, `Closes`, `Resolves`, or `Ref` followed by `ENG-&lt;number&gt;` or `#&lt;number&gt;`. The top line is `No ticket: ...` and contains no ticket pair. The `#L...` source anchors are not `#&lt;number&gt;` issue references. Full details: Coverage Accounts For The Whole Diff **Explanation** The Coverage table is populated and covers the changed behaviors. The two e2e rows cover the workspace toggle rename, placement, save behavior, and survey-editor label; the Rerun line names `survey-styling.spec.ts` and `js.spec.ts`. Manual rows cover the Appearance page and editor, all 15 locale updates, and documentation/image updates, with observed outcomes. The description includes screenshots, and Open gaps explicitly says `none`. The remaining diff changes are comments, test comments, documentation, localization metadata, or the corresponding loading presentation. ### Usage-based review receipt - Mode: Continue automatically - Reviewed files: 17 - Waived: $4.25 (charged $0.00) - View usage details > [!NOTE] > This review exceeded your plan’s limits and used usage-based reviews—free during trial, billed after paid activation unless disabled. **Manage usage-based reviews**. Comment `@the reviewer help` to get the list of available commands.
#9627
2026-10-21 00:26
cubic
review-body
—summary
**No issues found** across 38 files View guided diff | Turn on auto-fix | Re-trigger the reviewer
#9627
2026-10-21 00:31
greptile
issue-comment
—summary
Confidence Score: 5/5 **[Medium impact]** Safe to merge within the reviewed scope; no blocking issues were identified. Reviews (1) · Last reviewed commit: "fix(look): make the survey-level styling..." · Reviewed by the reviewer