Skip to content

Increased retention offer code entropy from 16-bit to 32-bit#26738

Merged
sagzy merged 1 commit intomainfrom
increase-retention-offer-code-entropy
Mar 9, 2026
Merged

Increased retention offer code entropy from 16-bit to 32-bit#26738
sagzy merged 1 commit intomainfrom
increase-retention-offer-code-entropy

Conversation

@sagzy
Copy link
Copy Markdown
Contributor

@sagzy sagzy commented Mar 9, 2026

closes https://linear.app/ghost/issue/BER-3422/increase-retention-code-entropy

  • retention offer codes now use Uint32Array instead of Uint16Array, generating 8-character hex codes instead of 4-character ones, increasing the code space from ~65K to ~4.3 billion possible values
  • also extracted the offer name generation logic and added unit tests, to make sure we stay under the 40-char max. limit imposed by Stripe on coupon names

Co-authored-by: Kevin Ansfield kevin@ghost.org

closes https://linear.app/ghost/issue/BER-3422/increase-retention-code-entropy

- retention offer codes now use Uint32Array instead of Uint16Array, generating 8-character hex codes instead of 4-character ones, increasing the code space from ~65K to ~4.3 billion possible values
- also extracted the offer name generation logic and added unit tests, to make sure we stay under the 40-char max. limit imposed by Stripe on coupon names

---------

Co-authored-by: Kevin Ansfield <kevin@ghost.org>
@sagzy sagzy requested a review from mike182uk March 9, 2026 14:02
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 9, 2026

Walkthrough

The changes implement validation constraints for retention offers by capping the maximum retention duration at 99 months and centralizing error messaging. A new generateRetentionOfferName function in offer-helpers consolidates the logic for constructing retention offer names with specific formatting rules. The hash generation for new offers is expanded from 4-hex-digit (Uint16) to 8-hex-digit (Uint32) format, along with a maximum name length constraint of 40 characters that may truncate the hash if necessary. Validation rules now enforce a 1–99 month range for both retention duration and free months, with updated error messages across the modal and tests.

Possibly related issues

  • TryGhost/Ghost-Security#142: Addresses the increase of retention-offer code bit-width from 16-bit (4 hex digits) to 32-bit (8 hex digits), which aligns with the hash generation expansion implemented in this PR.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately summarizes the primary change: increasing retention offer code entropy from 16-bit to 32-bit, which is reflected in the switch from Uint16Array to Uint32Array throughout the codebase.
Description check ✅ Passed The pull request description is directly related to the changeset, explaining the motivation (increased code space), the technical implementation (Uint32Array for 8-character hex codes), and the additional refactoring (extracted offer name generation logic and unit tests).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch increase-retention-offer-code-entropy

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/admin-x-settings/src/components/settings/growth/offers/offer-helpers.ts (1)

62-64: Consider using a direct export const declaration.

The constant is declared at the top without export, then re-exported at the bottom. This works but is slightly unconventional. Consider declaring it as export const directly on line 1 for cleaner code.

♻️ Suggested refactor
-const MAX_RETENTION_OFFER_NAME_LENGTH = 40;
+export const MAX_RETENTION_OFFER_NAME_LENGTH = 40;

Then remove lines 62-64:

-export {
-    MAX_RETENTION_OFFER_NAME_LENGTH
-};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin-x-settings/src/components/settings/growth/offers/offer-helpers.ts`
around lines 62 - 64, The constant MAX_RETENTION_OFFER_NAME_LENGTH is declared
without export and then re-exported at the bottom; change it to a direct
exported declaration (export const MAX_RETENTION_OFFER_NAME_LENGTH = ...) where
it’s originally defined and remove the separate export block (the export {
MAX_RETENTION_OFFER_NAME_LENGTH };) to simplify and clarify the module’s API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/admin-x-settings/src/components/settings/growth/offers/offer-helpers.ts`:
- Around line 62-64: The constant MAX_RETENTION_OFFER_NAME_LENGTH is declared
without export and then re-exported at the bottom; change it to a direct
exported declaration (export const MAX_RETENTION_OFFER_NAME_LENGTH = ...) where
it’s originally defined and remove the separate export block (the export {
MAX_RETENTION_OFFER_NAME_LENGTH };) to simplify and clarify the module’s API.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0caa826e-518a-4cc1-a8ef-99d232fc411d

📥 Commits

Reviewing files that changed from the base of the PR and between be0ea50 and c1b8083.

📒 Files selected for processing (4)
  • apps/admin-x-settings/src/components/settings/growth/offers/edit-retention-offer-modal.tsx
  • apps/admin-x-settings/src/components/settings/growth/offers/offer-helpers.ts
  • apps/admin-x-settings/test/acceptance/membership/offers.test.ts
  • apps/admin-x-settings/test/unit/components/settings/growth/offers/offer-helpers.test.ts

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c1b8083ccc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


const isValidMonthDuration = (value: number): boolean => {
return Number.isInteger(value) && value > 0;
return Number.isInteger(value) && value > 0 && value <= MAX_RETENTION_OFFER_MONTHS;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle legacy retention offers above 99 months

The new value <= MAX_RETENTION_OFFER_MONTHS check rejects any existing retention offer whose duration_in_months is above 99, even if the admin only edits display text. Because onValidate runs before handleSave, those legacy offers can no longer be saved in their current form once opened in this modal, which is a backward-compatibility regression for stores that already created long-duration retention offers under the previous rules.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acceptable, as feature is still in private beta and the limit is anyways much higher than the typical use case for a retention offer

@ErisDS
Copy link
Copy Markdown
Member

ErisDS commented Mar 9, 2026

🤖 Velo CI Failure Analysis

Classification: 🔴 HARD FAIL

  • Workflow: CI
  • Failed Step: Load Image
  • Run: View failed run
    What failed: Docker login to ghcr.io failed due to a context deadline exceeded error
    Why: The root cause of the failure is a Docker login error to the GitHub Container Registry (ghcr.io) due to a context deadline exceeded. This is an infrastructure issue, not a code problem, as it indicates a network or service availability problem on the CI environment.
    Action:
    This is likely a temporary issue with the GitHub Container Registry. Try rerunning the workflow, and if the problem persists, the infrastructure team should investigate the registry connection issues.

@sagzy sagzy merged commit 6d81157 into main Mar 9, 2026
51 of 56 checks passed
@sagzy sagzy deleted the increase-retention-offer-code-entropy branch March 9, 2026 14:38
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.

3 participants