Found while implementing #10159 (the write half of the same window). Filed separately rather than widening that card, and deliberately not fixed there: closing it would change what a real population observes, which #10159's fix was explicitly scoped not to do.
Observation
SettingsService.loadRows branches on if (this.engine) exactly as upsertRow does:
privateasyncloadRows(namespace: string,userId: string|null): Promise<SettingsRow[]>{if(this.engine){/* … engine.find(this.objectName, …) … */}returnthis.memory.filter(/* … */);}The engine is bound in one place — SettingsServicePlugin registers a kernel:ready hook from its start() and calls bindEngine inside it. Hooks fire in registration order and every plugin's init() runs before any plugin's start(), so every kernel:ready hook registered from an init() runs before that bind (the mechanism is written up in full on #10159).
So a get() / getNamespace() issued in that window reads the in-memory fallback, which at boot holds nothing — and the caller receives the manifest's declared default, with locked: false and no diagnostic, while a real persisted row with a different value sits in sys_setting unread.
Measured
The window itself is measured on #10159's branch (claude/issue-10159-settings-write-before-bind), where the in-window probe records:
serviceResolvableAtReady : true
engineBoundAtReady : false
readAtReady : resolved:"never" <- the manifest default
"never" is the specifier's declared default; the read is answered without the engine ever being consulted.
What is NOT measured — and why this is graded finding
No shipped in-window reader has been identified. The repo has ten-odd settings.get(...) / getNamespace(...) call sites (resolve-authz-context.ts, auth-plugin.ts, lifecycle-service.ts, email-plugin.ts, sms-plugin.ts, storage-service-plugin.ts, …), and whether any of them is reached from a kernel:ready hook registered during init() has not been established. That is the measurement this card needs before it is worth acting on: without it there is no evidence anyone is harmed, and the fix would be speculative.
Why it is worth recording anyway
The write half was recorded on the same reasoning and turned out to be real. And the read half is the quieter of the two: a wrong-but-plausible value (the declared default) is harder to notice than a missing one, and the locked: false that accompanies it is an assertion about persisted state made without reading persisted state.
Note on scope, if this is ever picked up
The refusal shape used for the write half is not transferable here. #10159's fix deliberately leaves reads open, because refusing an ordinary boot-time read would turn a correct startup sequence into an error — an in-window read of a defaulted setting is a legitimate, common thing to do. Any fix here has to answer "what should a boot-time reader see instead?", which is a different question from the write side's "when is the earliest safe moment to write?".
Adjacent, not a duplicate
#10221 also concerns a boot-time settings read, but on the far side of this window: there the read reaches the database and errors with sys_settingno such table (so the engine was already bound). This one never reaches the database at all, and produces no log line of any kind.
Filed unassigned.
Generated by Claude Code
Found while implementing #10159 (the write half of the same window). Filed separately rather than widening that card, and deliberately not fixed there: closing it would change what a real population observes, which #10159's fix was explicitly scoped not to do.
Observation
SettingsService.loadRowsbranches onif (this.engine)exactly asupsertRowdoes:The engine is bound in one place —
SettingsServicePluginregisters akernel:readyhook from itsstart()and callsbindEngineinside it. Hooks fire in registration order and every plugin'sinit()runs before any plugin'sstart(), so everykernel:readyhook registered from aninit()runs before that bind (the mechanism is written up in full on #10159).So a
get()/getNamespace()issued in that window reads the in-memory fallback, which at boot holds nothing — and the caller receives the manifest's declareddefault, withlocked: falseand no diagnostic, while a real persisted row with a different value sits insys_settingunread.Measured
The window itself is measured on #10159's branch (
claude/issue-10159-settings-write-before-bind), where the in-window probe records:"never"is the specifier's declareddefault; the read is answered without the engine ever being consulted.What is NOT measured — and why this is graded
findingNo shipped in-window reader has been identified. The repo has ten-odd
settings.get(...)/getNamespace(...)call sites (resolve-authz-context.ts,auth-plugin.ts,lifecycle-service.ts,email-plugin.ts,sms-plugin.ts,storage-service-plugin.ts, …), and whether any of them is reached from akernel:readyhook registered duringinit()has not been established. That is the measurement this card needs before it is worth acting on: without it there is no evidence anyone is harmed, and the fix would be speculative.Why it is worth recording anyway
The write half was recorded on the same reasoning and turned out to be real. And the read half is the quieter of the two: a wrong-but-plausible value (the declared default) is harder to notice than a missing one, and the
locked: falsethat accompanies it is an assertion about persisted state made without reading persisted state.Note on scope, if this is ever picked up
The refusal shape used for the write half is not transferable here. #10159's fix deliberately leaves reads open, because refusing an ordinary boot-time read would turn a correct startup sequence into an error — an in-window read of a defaulted setting is a legitimate, common thing to do. Any fix here has to answer "what should a boot-time reader see instead?", which is a different question from the write side's "when is the earliest safe moment to write?".
Adjacent, not a duplicate
#10221 also concerns a boot-time settings read, but on the far side of this window: there the read reaches the database and errors with
sys_settingno such table(so the engine was already bound). This one never reaches the database at all, and produces no log line of any kind.Filed unassigned.
Generated by Claude Code