Skip to content

[pull] main from TryGhost:main#1088

Merged
pull[bot] merged 9 commits into
code:mainfrom
TryGhost:main
Apr 22, 2026
Merged

[pull] main from TryGhost:main#1088
pull[bot] merged 9 commits into
code:mainfrom
TryGhost:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented Apr 22, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

vershwal and others added 9 commits April 22, 2026 10:35
…paths" (#27502)

Reverts #27479

Reverting this for now — the tests passed on my branch, but it wasn’t rebased onto the latest main.
…27504)

ref #19627
[HKG-1709](https://linear.app/ghost/issue/HKG-1709/deduplicate-concurrent-cache-misses)

- We're preparing to merge changes to the Redis cache adapter(concurrent
read deduplication). These tests lock in the current error-handling
behavior, so any behavioral regression is caught immediately.
- The current adapter silently swallows errors in several paths and the
exact behavior isn't documented by existing tests. The concurrent read
deduplication PR will change the `get` method structure to share
in-flight promises, which makes error paths more sensitive — one failure
now affects all callers waiting on the same key. We need to know exactly
how errors behave today before changing anything.
ref https://linear.app/ghost/issue/ONC-1669

- in change #27461, we updated the
member subscriptions API response for comped subscriptions: from a
hardcoded "USD" currency, to reading the actual currency of the tier.
However, some sites in production seem to have a comped subscription on
the Free tier, which does not have a currency
- This fix reverts back to using a hardcoded "USD" currency for comped
subscriptions
ref https://linear.app/ghost/issue/HKG-1708

When a class instance is cloned, private method calls start to throw

  TypeError: Receiver must be an instance of class AdapterCacheRedis
      at AdapterCacheRedis.get (.../AdapterCacheRedis.js:206:54)

The api-framework pipeline clones the entire controller including the
cache adapter _.cloneDeep before wrapping each method, which 
causes this error when using in production. We've got a regression
test now to ensure we catch it in future.
no ref

For i18n, we had to check if several different paths had changed to determine if the job should run. If more packages start to trigger i18n tests, the condition has to be updated and get more complicated. By using nx tags to dictate to the task graph which packages should trigger i18n tests, the configuration gets a lot simpler.

For admin, it was a simple matter of updating the condition to look in the affected packages list.
ref ONC-1656

Prevents the whole Stripe Service from erroring if the billing portal
manager encounters an error while trying to update the billing portal
configuration. This can happen if the billing portal configuration
that Ghost has created has been made the default one by the Stripe
Account owner.
ref https://linear.app/ghost/issue/BER-3544

- the `memberCount` getter on the `membersStats` service was summing
only `paid` + `free` + `comped`
- user-visible impact is narrow: the "existing members will be updated"
notice on the CSV import modal is gated by this getter, so a site with
only gift members would see the notice hidden
- defaulted `gift = 0` in the destructure to guard against admin being
deployed ahead of the API change, with a TODO to remove once the API is
guaranteed to include gift everywhere
ref https://linear.app/ghost/issue/HKG-1708

Previous fix (#27508) renamed # private methods to _ to dodge the
"Receiver must be an instance of class AdapterCacheRedis" error from
api-framework's _.cloneDeep. That uncovered the next layer of the same
problem:

  TypeError: Illegal invocation
      at Socket.write (...)
      at Redis.sendCommand (...)
      at AdapterCacheRedis.prefixHash (.../AdapterCacheRedis.js:86:58)

The adapter was caching the ioredis client as `this.redisClient` in the
constructor. When api-framework deep-clones the controller (and the
cache adapter alongside it), `this.redisClient` on the clone is a
clone of the ioredis instance - prototype intact but no live socket.
Any call on it dives into socket.write and fails.

The old code accidentally got away with this because it only went
through cache-manager-ioredis, whose `getClient` is a
closure-captured arrow that returns the original client regardless of
which `this` it's called on. Functions are preserved by reference
under cloneDeep, so the closure protection carried through. Our
prefix-rotation work bypassed cache-manager and went directly to
this.redisClient, losing that protection.

The proper fix is upstream in api-framework: it should not be deep-
cloning controllers that hold live native resources. This commit is
the local workaround.
closes https://linear.app/tryghost/issue/NY-1239

- introduces the Automations area in the admin sidebar (after Comments)
as a static skeleton so backend devs have a visual target to wire
dynamic behaviour against
- gated entirely behind the existing private `automations` flag so it
stays invisible until we are ready to ship
- new view lives in apps/posts (alongside Comments and Tags) and is
built with Shade primitives (ListHeader) and components (Table, Badge) —
no API calls, no data hooks

Co-authored-by: Evan Hahn <evan@ghost.org>
@pull pull Bot locked and limited conversation to collaborators Apr 22, 2026
@pull pull Bot added the ⤵️ pull label Apr 22, 2026
@pull pull Bot merged commit 545ccca into code:main Apr 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants