What happens
The app-manifest v2 schema documents @config.<key> as a first-class token:
Config context: IAppConfig-sourced values. @resolve:<key> resolved at manifest-load time; @config.<key> (trailing ? optional) at fetch time.
and endpointSource.url says such tokens "interpolate inline; unresolved URL tokens collapse to an empty string."
On a virtual application it always collapses. Measured on hydra-console (2026-07-31):
- Widget URL:
/apps/openregister/api/flow/@config.dispatchFlowId/state
occ config:app:set openbuild-hydra-console dispatchFlowId 29346336-… — and occ config:app:get reads it back correctly.
- The browser requests
/apps/openregister/api/flow//state?list=slots → 404.
The page carries no initial-state-openbuild-config element (enumerated in the DOM: openbuild ships currentUserGroups, builderSlug, builderVersion and nothing else), so there is nothing on the client for @config to read.
Why it matters
It is the only documented way for a manifest to name an instance-specific value. Every alternative is worse:
- hard-coding the value makes the manifest non-portable and silently wrong on any other instance;
@workspace.<key> is user-session scope, not deployment scope;
- an operator has no other place to say "this console watches that flow".
For the case that surfaced it — a slot-occupancy widget bound to a dispatch flow's uuid — the workaround is substituting the uuid by hand into the URL, recorded in hydra/console/README.md.
The failure mode is the bad kind
@config.dispatchFlowId in a URL produces /api/flow//state, which is a plausible-looking URL that 404s. The widget shows its emptyText, which reads as "no data yet" rather than "this token does not work". Nothing in the console except the network tab says otherwise.
If exposing app config to the client is not wanted, the honest alternative is to make an unresolved required token block the fetch and surface an error — the schema already describes that behaviour for params ("a REQUIRED token that stays unresolved blocks the fetch"), just not for url.
Related
What happens
The app-manifest v2 schema documents
@config.<key>as a first-class token:and
endpointSource.urlsays such tokens "interpolate inline; unresolved URL tokens collapse to an empty string."On a virtual application it always collapses. Measured on
hydra-console(2026-07-31):/apps/openregister/api/flow/@config.dispatchFlowId/stateocc config:app:set openbuild-hydra-console dispatchFlowId 29346336-…— andocc config:app:getreads it back correctly./apps/openregister/api/flow//state?list=slots→ 404.The page carries no
initial-state-openbuild-configelement (enumerated in the DOM: openbuild shipscurrentUserGroups,builderSlug,builderVersionand nothing else), so there is nothing on the client for@configto read.Why it matters
It is the only documented way for a manifest to name an instance-specific value. Every alternative is worse:
@workspace.<key>is user-session scope, not deployment scope;For the case that surfaced it — a slot-occupancy widget bound to a dispatch flow's uuid — the workaround is substituting the uuid by hand into the URL, recorded in
hydra/console/README.md.The failure mode is the bad kind
@config.dispatchFlowIdin a URL produces/api/flow//state, which is a plausible-looking URL that 404s. The widget shows itsemptyText, which reads as "no data yet" rather than "this token does not work". Nothing in the console except the network tab says otherwise.If exposing app config to the client is not wanted, the honest alternative is to make an unresolved required token block the fetch and surface an error — the schema already describes that behaviour for
params("a REQUIRED token that stays unresolved blocks the fetch"), just not forurl.Related
openspec/changes/hydra-flows-first-port) — the widget this blocked; shipped with the manual substitution.?list=projection the widget reads.