Skip to content

fix: ask the config service whether a site exists, not head.html - #270

Merged
tripodsan merged 50 commits into
mainfrom
headhtml
Aug 18, 2026
Merged

fix: ask the config service whether a site exists, not head.html#270
tripodsan merged 50 commits into
mainfrom
headhtml

Conversation

@benpeter

@benpeter benpeter commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Description

before

  • /head.html decided whether a site exists, so a site with no head.html on the ref was answered
    404: Unable to retrieve AEM branch
  • an unpreviewed ref, a preview host answering 500 and a site behind Helix authentication came
    back as that same 404
  • a throw from an unreachable preview host reached the browser as a bare 500
  • getPageTemplate swallowed a config store failure and answered the default template over a
    document that exists
  • the store came from admin.hlx.page/ping

now

  • one read of config.aem.page at scope=pipeline answers existence, head.html and the store.
    404 means no such site, any other non-200 is a 503
  • contentSource.url names the store. a config that names no store reads as legacy, and names the
    site in a warning
  • head.html comes off that same read, so a site behind Helix authentication renders too
  • UpstreamError and reach() in src/utils/upstream.js: a read with no answer to use is a 503.
    the upstream is named in the body and in x-error, and the author is told to retry or ask an
    admin
  • a save asks that same one read. for a site the config service does not know, it is refused 404
    where it used to go to da-admin
  • admin.hlx.page is gone. the last call was the site token exchange in src/routes/cookie.js,
    which hardcoded the host and now posts to ${env.AEM_API}/auth/adobe/exchange
  • dev/lookup-shim.js and npm run dev:lookups stand in for the config service locally
  • [env.dev] now declares HLX_CONFIG_SERVICE_TOKEN, without which npm run dev 401s against
    config.aem.page. cloudflare/wrangler-action v3 to v4, since v3's wrangler ignores secrets.
    same two lines as chore(deps): update cloudflare/wrangler-action action to v4 #214

Related Issue

fixes #258. follows the review on #263.

Testing

live against config.aem.page, with the pre-change branch on a second port. the 404 body for a site
that does not exist changed on purpose, from Not found: Unable to retrieve AEM branch to
404: Site not found.

  • a site whose config has contentSource: one read, routed to
    https://api.aem.live/{org}/sites/{site}/source/index.html. a legacy site: routed to
    https://admin.da.live/source/{org}/{site}/.... a site the config service does not know: 404
  • a site whose config is still cached without contentSource: routed to da-admin, with
    named no content source, reading {org}/{site} as legacy in the log
  • a write to a source-bus site: 405 before any store is asked. a write to a site the config service
    does not know: 404 in plain text, also before any store. a write to a legacy site that exists:
    routed to https://admin.da.live/source/{org}/{site}/...
  • a save while config.aem.page cannot be reached: 503, no store asked, nothing written
  • /gimme_cookie on a site behind Helix authentication: 200 with an auth_token and a 24 hour
    site_token. on a site that needs no site token, the auth cookie alone. api.aem.live and
    admin.hlx.page, asked in the same second, mint a byte-identical token
  • an anonymous request and an IMS bearer are both 401
  • not live: the legacy store end to end, since da-admin-local was not running, and the editor
    config 503, which has unit tests only

dropping the second lookup from a write changed no routing. both trees ran 13 behaviours per scope
against a scripted config service, and of 169 outcome pairs none writes to a different store.

Risks

HLX_CONFIG_SERVICE_TOKEN has to be set on da-ue and da-ue-stage before the first deploy. the
deploy fails red without it.

…s the 503

red. head.html still decides whether a site exists, so a site not yet previewed
on the requested ref is refused at 404 with its document already read and
discarded. an upstream that cannot be reached still reports the failure in a
return value, and a preview host that throws still reaches the browser as a 500
with no body.

the full suite aborts on load, because src/storage/site.js does not exist yet.
…ce means

head.html decided whether a site exists, so a site not yet previewed on the
requested ref was refused at 404 with its document already read and discarded.
config.aem.page answers both questions in one read: whether there is such a
site, and which store holds its content. content.source.url is the field
helix-admin sets x-api-upgrade-available from, so it is the boolean /ping
conveyed. the /ping probe and HLX_ADMIN are gone.

three upstreams reported a failure by returning a value, and two swallowed one.
reach() names the upstream and rethrows a typed UpstreamError, and each route
entry point catches once and builds the 503 its method takes. x-error names the
upstream every time, not only on the site lookup.

a 401 or 403 cannot be retried, so it is not a 503. neither is a 404. the
preview host and the config store fall back on all three and log the refusal,
and only a status that means no answer becomes an UpstreamError. a throw that
is not an UpstreamError reaches the worker boundary, which logs it and answers
500.

Relates to #258
…repo pins

HLX_ADMIN goes out of all three environments: its only reader was the deleted
/ping probe. dev points at dev/config-shim.js on 4713, which stands in for
config.aem.page so the worker runs locally without the shared secret, and
.gitignore gains the glob because npm start runs --env dev and wrangler reads
.dev.vars.dev before .dev.vars.

the deploy job had no npm ci, so wrangler-action installed its own default,
3.90.0, which has no secrets key and would deploy past a missing
HLX_CONFIG_SERVICE_TOKEN. da-admin and da-collab install and run npm run deploy
instead of using the action; this matches them. node 24, above the floor
wrangler and miniflare declare.
…aration

v3 installs its own wrangler 3.90.0, which has no secrets key in its config
schema, so `secrets = { required = [...] }` is ignored and a deploy without
HLX_CONFIG_SERVICE_TOKEN goes out green. v4 defaults to wrangler 4.

same two lines as #214.
…nd the head

red. the head arrives with the existence answer, so one pipeline read replaces the admin scope, and the source-bus flag comes off admin.hlx.page/ping again.
…t exists

green. the pipeline scope answers existence and head.html in one read, so the admin scope goes, and with it the CDN token and api key metadata the worker was reading. a write asks /ping only.
red. header-absent-on-5xx read as legacy, which sends a source-bus write to da-admin where nothing serves it back.
the header is read ahead of the status, so the edge dictionary still answers for an origin that is rate limited. a refusal without it now throws.
red. /ping answers 200 with no header for a source-bus site in exactly that window, since helix-admin reads the same config and swallows the failure.
red. it answered with the store-did-not-answer text, and no store was asked.
the store answer comes from the same config, so an outage that hides one hides the other. a wrong store cannot be walked back from, and the 503 now names the destination as undetermined rather than the store as unreachable.
@benpeter
benpeter marked this pull request as ready for review August 13, 2026 08:05
@tripodsan

Copy link
Copy Markdown
Contributor

I think it was once decided that we don't need to support the case, where a DA project uses the new helix6 API for preview/publish, but still uses the DA storage.

so, in order to simplify things, we don't need to read /ping at all.

  1. read config.aem.page/..?scope=pipeline (we need to add the content source, though)
  2. the config contains the head.html
  3. check the content-source if to use source bus (and api.aem.live).

@benpeter
benpeter marked this pull request as draft August 13, 2026 14:05
Comment thread src/utils/constants.js
Comment thread test/routes/da-admin.test.js
@mhaack

mhaack commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Q for @tripodsan: not sure if it is too early for this PR but would it be ok to already only use https://api.aem.live and no admin.hlx.page any more?

@tripodsan

Copy link
Copy Markdown
Contributor

Q for @tripodsan: not sure if it is too early for this PR but would it be ok to already only use https://api.aem.live and no admin.hlx.page any more?

with #270 (comment), you don't need admin.hlx.page at all

(exchange token should also work: https://api.aem.live/auth/adobe/exchange)

@tripodsan

tripodsan commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

config service pipeline scope now returns the contentSource (if it doesn't work for you, trigger a cache purge by changing the site config).

eg:

https://config.aem.page/main--da-hlx6--rofe/config.json?scope=pipeline

...
"contentSource": {
  "type": "markup",
  "url": "https://api.aem.live/rofe/sites/da-hlx6/source"
},
...

@benpeter
benpeter requested review from mhaack and tripodsan August 14, 2026 22:55
@benpeter
benpeter marked this pull request as ready for review August 14, 2026 22:55
Comment thread src/utils/upstream.js Outdated
Comment thread src/utils/upstream.js
@benpeter

benpeter commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

tested against stage (in sync with headhtml / 8a315e8)
against {ref}--{site}--{org}.stage-ue.da.live

reads

  • anonymous request, any site: 401 with the da:401 shell
  • legacy site with content in stage DA: 200, stage DA document
  • source-bus site: 200, from the source bus
  • site unknown to config service: 404 404: Site not found.
  • HEAD on known site: 200
  • HEAD on unknown site: 404

writes

  • to a source-bus site: 405 in plain text with Allow: GET, HEAD, OPTIONS
  • site unknown to config service: 404
  • legacy site: written through to stage DA

auth

  • /gimme_cookie from trusted origin: 200, auth_token
  • /gimme_cookie from untrusted origin: 403
  • GET with that cookie, no auth header: 200
  • production IMS bearer against the stage legacy site: 401

@tripodsan
tripodsan merged commit f4f1ff4 into main Aug 18, 2026
5 checks passed
@tripodsan
tripodsan deleted the headhtml branch August 18, 2026 12:45
benpeter added a commit that referenced this pull request Aug 18, 2026
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.

HTML source proxy returns 404 when head.html is unavailable

3 participants