fix: prevent daemon fsync crash and Pi catalog failures - #454
Conversation
There was a problem hiding this comment.
ℹ️ Both headline fixes are correct and independently verified. Two minor suggestions inline plus one doc-drift item below; nothing here blocks merge.
Reviewed changes
Three independent fixes — a Windows daemon crash, pi bare-model provider resolution, and an eager pi catalog request — each with new tests, no wire/schema/version changes.
-
Daemon config write stops fsyncing a read-only handle.
writeConfigFieldsnow opens the temp file once withopenSync(path, 'wx', 0o600)and does write →chmod→fsync→closeon that single write descriptor; the old code re-opened with'r', which failsEPERMon Windows becauseFlushFileBuffersrequires write access. -
fsyncPathnarrowed to the directory fsync. The helper survives but is now only used for the post-renameparent-directory sync, still skipped on Windows. -
pi gains a fourth provider-precedence tier. When a bare model id has no provider from
fallbackProviderorcred.knownProvider,createConfiguredRegistryscansmodelRegistry.getAll()for a model whoseidmatches and whosebaseUrlequalscred.baseUrl, accepting it only if the match is unique. -
readAgentCredentialhoisted above model parsing. Behavior-neutral — the function is pure overconfigfields. -
pi catalog request is gated on runtime availability.
use-agent-catalogs.tspassesnullparams (tayori's pause idiom) unlessuseAgentRuntimes()reportspi.status === 'available', ending the eageragent.catalogrequest that loggedFailed to load agent catalogwhen pi isn't installed.
ℓ️ Verification notes
I checked the two claims that mocked tests can't establish, since both fixes depend on real SDK and real data behavior:
ModelRegistry.getAll()does exist andModel.baseUrlis required in the installed@earendil-works/pi-aitypings, so the new registry scan is real rather than mock-only fiction. (The test's localinterface ModeldeclaresbaseUrl?: stringoptional — a small fidelity gap, noted as a nit below.)- pi's built-in
MODELStable (35 providers, 1057 models) containsopencode-go/deepseek-v4-flashat exactlyhttps://opencode.ai/zen/go/v1, andpackages/foundation/providers/src/catalog.tshas no opencode/zen service — so the account in the linked issue genuinely has noknownProvider, and the new endpoint tier is precisely the tier that resolves it. Separately, zero(modelId, baseUrl)pairs in that table map to more than one provider, so the uniqueness guard isn't a practical blocker today.
ℹ️ AGENTS.md provider-precedence documentation is now stale
packages/host/agent-adapter/AGENTS.md documents the provider-routed credential precedence as:
Precedence: model-ref → for pi, the resumed session's own last-routed provider (
lastPiModelChange, direct evidence) →config.knownProvider→ for pi, its first available provider
This PR adds a fourth tier (built-in endpoint match) and changes the documented outcome for a bare model id from "throws" to "resolved when provenance is unambiguous". That file isn't in the diff so I can't anchor a line comment, but it's the routing doc AGENTS.md points agents at before touching this code — worth updating in the same PR while the reasoning is fresh.
ℹ️ Nitpicks
-
The
getAll()endpoint scan is built unconditionally, then discarded whenfallbackProvider ?? cred.knownProvideralready resolves. Cheap at 1057 models and reads fine as-is, but the loop could sit behind the!providercheck if you'd rather not walk the table on the common path. -
pi-model.test.ts's localinterface ModeldeclaresbaseUrl?: string, whereas the real SDK type requires it. Making it required keeps the fixture honest and would catch a future fixture that forgets the field.
Claude Opus | 𝕏
PeronGH
left a comment
There was a problem hiding this comment.
Reviewed the three production changes. The fsync-through-the-writable-handle fix is correct: the old fsyncPath reopened the temp file read-only, so the sync targeted a different descriptor than the one that wrote the bytes. Write/chmod/fsync/close ordering and the post-rename directory fsync are preserved. Pi bare-model resolution correctly refuses ambiguous endpoint matches, and gating the Pi catalog on runtime availability leaves the other four agents untouched. CI green.

Summary
Verification
pnpm check:cipnpm test(2956 passed, 1 skipped)opencode-go/deepseek-v4-flashCloses #437
Closes #438