Skip to content

🐛 Fixed members being subscribed to all newsletters when choosing none - #30708

Merged
acburdine merged 2 commits into
mainfrom
fix-empty-newsletters-signup-defaults
Sep 14, 2026
Merged

acburdine merged 2 commits into
mainfrom
fix-empty-newsletters-signup-defaults

Conversation

@acburdine

Copy link
Copy Markdown
Member

A member reported that on a free plan, unticking every newsletter in Portal still subscribed them to all of them.

Portal's newsletter selection page sends newsletters: [] when nothing is ticked. Theme signup forms whose only newsletter inputs are unchecked data-members-newsletter checkboxes also send [], deliberately, to opt out of the defaults (apps/portal/src/data-attributes.js). The server turned that into undefined, which member creation treats as "not specified" and fills with every subscribe_on_signup newsletter.

This regressed twice:

Change

_validateNewsletters now returns [] for an empty list, and undefined only when no list was sent. The signup call site no longer turns a missing list into [] (?? []), so signups that don't send newsletters (Portal's signup and offer pages, the signup-form embed) still get the defaults.

Testing

Unit tests for a free signup sending [], a signup sending no list (still gets the defaults), and paid checkout metadata keeping "[]".

🤖 Generated with Claude Code

no ref

Unticking every newsletter on Portal's selection page sends
`newsletters: []`, and theme signup forms with only unchecked newsletter
checkboxes send the same to opt out of the defaults. Since
`_validateNewsletters` was introduced, an empty list came back as
`undefined`, which member creation reads as "not specified" and replaces
with every subscribe-on-signup newsletter. Paid signups got the same
treatment once checkout metadata went through that function too.

An explicit empty list is now kept, so the member signs up to nothing.
Only a missing list falls back to the defaults, which meant dropping the
`?? []` at the signup call site that turned a missing list into an empty
one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Advanced

Run ID: 9889bf7c-9993-494e-819d-2d1cd56402d3

📥 Commits

Reviewing files that changed from the base of the PR and between 26466c7 and dff3967.

📒 Files selected for processing (2)
  • ghost/core/core/server/services/members/members-api/controllers/router-controller.js
  • ghost/core/test/unit/server/services/members/members-api/controllers/router-controller.test.js

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (9)
  • GitHub Check: Build Ghost-CLI archive
  • GitHub Check: Acceptance tests (Node 24.20.0, mysql8)
  • GitHub Check: Legacy tests (Node 24.20.0, mysql8)
  • GitHub Check: Acceptance tests (Node 22.23.1, mysql8)
  • GitHub Check: Build Docker Images
  • GitHub Check: Legacy tests (Node 22.23.1, mysql8)
  • GitHub Check: Lint
  • GitHub Check: Detect Tinybird changes
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (5)
Review new or changed service boundaries for explicit dependency ownership, deterministic/idempotent initialisation, boot ordering, transaction and event semantics, cache coherence, and restart/multi-instance safety.

⚙️ CodeRabbit configuration file

Files:

  • ghost/core/core/server/services/members/members-api/controllers/router-controller.js
Review whether tests prove changed behaviour, meaningful error/edge paths, and externally observable contracts without coupling to implementation details.

⚙️ CodeRabbit configuration file

Files:

  • ghost/core/test/unit/server/services/members/members-api/controllers/router-controller.test.js
New source files must be TypeScript: flag new JS files as a required change unless exempt (DB migrations, apps/ember-admin/, tool/config files, scripts/, docker/, generated code).

⚙️ CodeRabbit configuration file

Files:

  • ghost/core/test/unit/server/services/members/members-api/controllers/router-controller.test.js
  • ghost/core/core/server/services/members/members-api/controllers/router-controller.js
Prioritise concrete correctness, security, data-integrity, compatibility, and regression risks.

⚙️ CodeRabbit configuration file

Files:

  • ghost/core/test/unit/server/services/members/members-api/controllers/router-controller.test.js
  • ghost/core/core/server/services/members/members-api/controllers/router-controller.js
New files are TypeScript: Fail if the PR adds a new .js/.jsx/.cjs/.mjs source file, unless it is: a DB migration (ghost/core/core/server/data/migrations/), under apps/ember-admin/, a tool/config file, under scripts/ or docker/, or generated...

📄 CodeRabbit inference engine (Custom checks)

Files:

  • ghost/core/test/unit/server/services/members/members-api/controllers/router-controller.test.js
  • ghost/core/core/server/services/members/members-api/controllers/router-controller.js
🔇 Additional comments (2)
ghost/core/core/server/services/members/members-api/controllers/router-controller.js (1)

1100-1100: LGTM!

Also applies to: 1152-1152

ghost/core/test/unit/server/services/members/members-api/controllers/router-controller.test.js (1)

2455-2462: LGTM!


Walkthrough

The signup flow passes the newsletters field without applying an empty-array default. Newsletter validation returns undefined for omitted input and [] for explicit empty input. Tests cover checkout metadata, magic-link token data, and direct validation results.

Suggested reviewers: evanhahn

Priority: ⚪ Not assessed

Change: Bug fix

Merge Risk: ⚪ Minimal · up to dff39

The signup flow now preserves explicit opt-outs while retaining defaults for omitted newsletters, with matching test coverage.

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: preventing members from being subscribed to all newsletters when they choose none.
Description check ✅ Passed The description directly explains the newsletter subscription bug, the behavior change, affected signup flows, and test coverage.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Type-Safe Boundaries ✅ Passed PASS. The changed signup path sends the HTTP field to _validateNewsletters, which now checks Array.isArray before reading length, calling .some, or mapping entries. Non-array boundary values r…
New Files Are Typescript ✅ Passed The authoritative pull-request diff contains two modified pre-existing .js files and no added files. The check fails only when the PR adds a new .js, .jsx, .cjs, or .mjs source file. Therefo…
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-empty-newsletters-signup-defaults

Warning

Some tools did not complete. Review the errors below.

🔧 ast-grep (0.45.3)
ghost/core/test/unit/server/services/members/members-api/controllers/router-controller.test.js

ast-grep timed out on this file


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

@nx-cloud

nx-cloud Bot commented Sep 11, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit dff3967

Command Status Duration Result
nx run ghost:test:ci:integration ✅ Succeeded 4m 45s View ↗
nx run ghost:test:integration ✅ Succeeded 3m 47s View ↗
nx run ghost:test:ci:e2e ✅ Succeeded 4m 16s View ↗
nx run ghost:test:legacy ✅ Succeeded 3m 9s View ↗
nx run ghost:test:e2e ✅ Succeeded 2m 54s View ↗
nx run ghost-monorepo:lint:boundaries ✅ Succeeded 29s View ↗
nx run-many -t test:unit -p ghost ✅ Succeeded 35s View ↗
nx run-many -t lint -p ghost,ghost-monorepo ✅ Succeeded 20s View ↗
Additional runs (4) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-09-11 23:30:41 UTC

Copy link
Copy Markdown
Member Author

CI failure here (Acceptance tests (Node 22.23.1, mysql8) and Acceptance tests (Node 24.20.0, mysql8)) isn't from this PR's diff — the actual test suites (2487/2487 tests, 159/159 files) pass cleanly in both jobs. Both fail in a later, unrelated step: docker run minio/minio ... returns pull access denied for minio/minio, repository does not exist or may require 'docker login' (exit code 125). That's Docker Hub rejecting the pull, not anything about newsletters/members code.

I confirmed this reproduces on main at the same base commit this PR branches from (run 34640706678, jobs 103399818445/103399818462) with the identical pull access denied for minio/minio error, so it predates and is unrelated to this change. I've queued a re-run of the failed jobs on this PR's run; if it's still red after that it'll need infra attention (Docker Hub auth/rate limiting for the minio/minio image) rather than a code change here.

On the actual code: _validateNewsletters already returns [] (not falsy) for an empty array — [] is truthy in JS, so !requestedNewsletters only catches undefined/null, and the separate .length === 0 check is what returns [] explicitly. That part looks correct as written.


Generated by Claude Code

no ref

A truthy, non-array value with a length of 0 (for example {length: 0}
in a hand-crafted request body) satisfied the new empty-list check and
was returned as [], the same as a real empty array, instead of falling
through to the defaults like any other malformed value. Requiring
Array.isArray keeps that check limited to actual arrays.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2bVf68ihCcTAfhkWGBkAr

Copy link
Copy Markdown
Member Author

Addressed CodeRabbit's "Type-Safe Boundaries" finding: _validateNewsletters now requires Array.isArray(requestedNewsletters) before treating input as a list, so a non-array truthy value (e.g. { length: 0 }) falls through to undefined (the defaults) instead of being read as an explicit empty list. Added a unit test for that case. Pushed in dff3967.


Generated by Claude Code

@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.73%. Comparing base (8afbca5) to head (dff3967).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...mbers/members-api/controllers/router-controller.js 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #30708      +/-   ##
==========================================
+ Coverage   67.66%   67.73%   +0.07%     
==========================================
  Files        1676     1676              
  Lines       60517    60519       +2     
  Branches    10465    10466       +1     
==========================================
+ Hits        40947    40991      +44     
+ Misses      17250    17212      -38     
+ Partials     2320     2316       -4     
Flag Coverage Δ
e2e-tests 70.47% <0.00%> (+0.08%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@acburdine
acburdine merged commit 46b4f39 into main Sep 14, 2026
58 checks passed
@acburdine
acburdine deleted the fix-empty-newsletters-signup-defaults branch September 14, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants