Repository navigation
Description
sync-models: parseable-but-wrong-shaped opencode.jsonc crashes raw — non-object provider/models values throw unnamed TypeErrors in run() and in the JSONC tree editor
Repo: developing-today/code · Main: 3a3d59b9 · File: bin/sync-models.mjs — run() provider derefs at :496, :559, :598; prov.models = prov.models || {} at :498/:561; tree editor setJsoncPath at :382–391, setJsoncMember at :364–365, removeJsoncPath at :398–404; top-level catch at :707.
Evidence (verified this run, real module)
sync-models reads the user's opencode.jsonc with its tolerant JSONC parser (parseJsonc, #704) — so a config that is parseable but has the wrong shape (a hand edit, a merge artifact, another tool's output: "google": "oops", "models": "oops", "google": [1,2], or no provider key at all) passes parsing and then crashes with a raw TypeError that names neither the config file nor the offending key. run().catch (:707) prints the bare stack as Fatal error: and exits 1; the sync service then fails identically on every run until a human guesses which key is wrong.
Laptop probe (verbatim extraction of parseJsonc / parseJsoncTree / setJsoncPath / removeJsoncPath from the real file — the extraction method bin/sync-models.test.mjs itself uses; sync-models.mjs md5 551299a6be52401fe7563623cfb8acc2, identical on laptop and sandbox main; probe retained at laptop ~/git/fleet/probe-2026-10-30-laneA-syncshape2/):
- CONTROL set:
setJsoncPath(well-formed, ['provider','google','models','m1'], …)→ OK, key inserted. - CONTROL remove:
removeJsoncPath(well-formed multi-line, […,'m0'])→ OK; removing an absent member returns the text unchanged. setJsoncPathwith"google": "oops"→TypeError: Cannot read properties of undefined (reading 'find')—setJsoncPathwalksobject = member.value(:391) into the scalar/array tree node, whosemembersisundefined, andobject.members.find(:385 / :365) throws.- Same throw with
"google": [1, 2](array node also has nomembers) and with"models": "oops"as the final intermediate. removeJsoncPaththrough a scalar or array intermediate → sameTypeError(:401/:404).run()-level shapes, against realparseJsoncoutput: a config with noproviderkey hitsconfig.provider['openai-codex'](:559, and the antigravity block at :496 the same way) →TypeError: Cannot read properties of undefined (reading 'openai-codex')."models": "oops"survivesprov.models = prov.models || {}(:498/:561 — a non-empty string is truthy) and the firstprov.models[m] = …assignment throwsTypeError: Cannot create property 'm1' on string 'oops'(module strict mode).
No file is corrupted by any of these — edits are applied to the text in memory and the write happens only after all edits succeed — so this is an availability/diagnosability bug, not a data-loss bug. The failure is total, though: one wrong-shaped provider entry blocks syncing for every provider, including the unrelated healthy ones, and the error gives the operator nothing actionable.
Why it matters
sync-models is the unattended service that keeps the laptop's OpenCode model config current. Its parser was deliberately made tolerant (#704) because the config is hand-edited JSONC; tolerance at the syntax layer makes shape tolerance at the structure layer the remaining gap. Today one stray string where an object belongs turns into a permanently failing service with a stack trace that doesn't say which provider, which key, or which file — the exact failure mode the fleet has been filing across the other config consumers (#2800, #2738).
Acceptance criteria
- A parseable-but-wrong-shaped
opencode.jsoncproduces a named, actionable error (config path + the offending key path, e.g.provider.google: expected object, got string) — never a bareCannot read properties of undefined/Cannot create propertyTypeError. - Triage pins the treatment per site: fail-fast with the named error for the
provider/modelscontainers the sync must write into, or skip-and-warn for a provider entry the sync would only read — but a wrong-shaped entry for one provider must not silently pass, and the chosen behaviour is the same inrun()and in the tree editor. -
setJsoncPath/removeJsoncPathrefuse a non-object intermediate node with an error naming the key path instead of dereferencingmemberson it. - A missing
providerkey is treated as "nothing to sync" (or the named error above), not a crash. - Tests in
bin/sync-models.test.mjspin: scalar provider entry, array provider entry, scalarmodels, missingproviderkey — plus the existing well-formed set/remove controls stay green. - No behaviour change for well-formed configs: same edits, same backup, same write-only-after-all-edits-succeed ordering.
Dedup: searched open + closed issues for sync-models + jsonc/shape/provider — no filing covers the config-shape crash. #2790 is the policy file's family validation (different input file, loadPolicy/allowedModelIds); #1799 is stale-entry reconciliation; #704 (closed) was syntax-level parsing only. #2827 is provider-catalog-sync's upstream-response shape, a different tool. NOT self-solved: fail-fast vs skip-and-warn per site is triage's pin.
Activity
dezren39 commentedon Oct 30, 2026
🤖 Agent note · filed
dezren39 commentedon Oct 31, 2026
🤖 Design-architect pass 26 (2026-10-31) — agent note: this issue is adopted as part of Phase 2 of design #3068 ("parsed is not validated"). Triage: do NOT re-file, re-scope or split it — record the phase structure instead. The design records the rule this phase lands under: validate the shape you deref at the boundary, before any side effect; absence of a relied-on field is a shape failure, not an empty collection; failure is the tool's own named error/diagnostic (naming file + key for config files, the module's own error type for response readers) with no partial work; one shared probe matrix (null, array, scalar, {}, each relied-on field wrong-typed). Phase-specific decisions are on #3068 and in files/designs/code-parsed-is-not-validated.md (goal workspace).
34 remaining items
dezren39 commentedon Nov 3, 2026
agent-note:v1
state: picked-up
queue: repo-health-code-pilot
task: 8612 (triage, worker repo-worker-code, lane 262eb403-3347-4a7c-ba6b-e7bc263ca853)
by: openchamber-scheduled-lane
at: 2026-11-03T17:40Z
Resume of parked triage 8612 (third loop-back coverage pass for this issue). Prior pass re-verified the defect live and routed labels; comment posting was blocked by GitHub secondary rate limit. Re-validating against current
mainbefore completing.dezren39 commentedon Nov 3, 2026
agent-note:v1
state: triaged (no new work — owned by design #3068 Phase 2; verdict re-confirmed)
queue: repo-health-code-pilot
task: 8612 (triage, worker repo-worker-code, lane 262eb403-3347-4a7c-ba6b-e7bc263ca853)
by: openchamber-scheduled-lane
at: 2026-11-03T17:45Z
link: design #3068 (Phase 2 — parsed files & upstream responses in tools)
Third loop-back coverage triage. Substantive triage was done by task 3981 (2026-11-01) and re-confirmed by task 7981 (2026-11-03 13:48Z); this pass re-ran the matrix so the verdict is not stale evidence, then fixed the routing state that kept re-queueing it.
Re-verified this run on current
main821c02105(bin/sync-models.mjsmd5f1d1ea88— unchanged since the22892c316verification). Verbatim extraction ofparseJsonc/parseJsoncTree/setJsoncPath/removeJsoncPathfrom the real module, same method asbin/sync-models.test.mjs:setJsoncPathwith"google": "oops"→TypeError: Cannot read properties of undefined (reading 'find')setJsoncPathwith"google": [1, 2]→ same unnamed TypeErrorremoveJsoncPaththrough a scalar intermediate → same unnamed TypeErrorrun()-level sites unchanged:prov.models = prov.models || {}at :541 still passes truthy non-objects through to a laterCannot create propertyTypeError;config.provider[targetProvider]derefs still assume aproviderobject.So the defect is present exactly as filed; the shape guards still have not landed. No open or merged PR implements it (
gh pr list --search 2832→ only unrelated #2250, #121).Routing — nothing to enqueue. Design #3068 is open and still lists #2832 in Phase 2; its adoption note on this issue forbids re-filing, re-scoping or splitting. The per-site treatment decision (fail-fast named error vs skip-and-warn) stays with Phase 2, per the design rule "validate the shape you deref at the boundary".
Label sync: removed
queue/queuedwork order so coverage stops minting no-op triages; setstatus/waiting-dependency(#3068 Phase 2) so the work order lives with the design. When Phase 2 lands the fix,status/waiting-dependencyclears and the issue closes with the design's acceptance.Process note for the join (unchanged, still worth fixing): design-adopted origins should be excluded from the coverage join or carry a live "owned by design" task; until then,
status/waiting-dependencyis the marker that this issue is not waiting on a queue worker.dezren39 commentedon Nov 3, 2026
agent-note:v1
state: done
queue: repo-health-code-pilot
task: 8612 (completed, outcome
superseded)by: openchamber-scheduled-lane
at: 2026-11-03T17:47Z
No branch, no PR, no follow-up task. Issue stays open and owned by design #3068 Phase 2; defect re-verified live on current main
821c02105. In-flight labels cleared;status/waiting-dependency+status/triagedretained as the routing marker.dezren39 commentedon Nov 3, 2026
🤖 Agent note · handed off (re-handoff)
dezren39 commentedon Nov 3, 2026
🤖 Agent note · handed off
handed_off(prior handoff lease expired, where applicable). Fresh revalidation: issue sync-models: parseable-but-wrong-shaped opencode.jsonc crashes raw — non-object provider/models values throw unnamed TypeErrors in run() and in the JSONC tree editor #2832 is OPEN, no hard-stop labels (held / user/owned / do-not-merge / do-not-auto-edit absent).dezren39 commentedon Nov 3, 2026
agent-note:v1
state: picked-up
queue: repo-health-code-pilot
task: 8612 (triage, worker repo-worker-code, lane 9aca43b4-5439-4939-9f71-323f5d32521b)
by: openchamber-scheduled-lane
at: 2026-11-03T20:45:56Z
Resumed the queue claim and revalidated issue #2832 against current main 7d3dbd0 and design #3068. Working tree: /home/user/git/fleet/8612-2832-triage-sync-shape on queue/8612-2832-triage-sync-shape. No hard-stop labels; the issue and owning design remain open. Continuing from the saved triage finding rather than opening duplicate fix work.
dezren39 commentedon Nov 3, 2026
agent-note:v1
state: triaged (live defect; owned by design #3068 Phase 2)
queue: repo-health-code-pilot
task: 8612 (triage, worker repo-worker-code, lane 9aca43b4-5439-4939-9f71-323f5d32521b)
by: openchamber-scheduled-lane
at: 2026-11-03T20:49:03Z
link: #3068
Revalidated on current main 9310db4. Issue #2832 remains OPEN; design #3068 remains OPEN and Phase 2 still adopts #2832. No implementing PR found in the current all-state search for #2832, and there are no hard-stop labels.
Fresh reproduction from the real current-main source: extracted parseJsoncTree/setJsoncPath/removeJsoncPath verbatim; a scalar provider.google intermediate throws the unnamed TypeError "Cannot read properties of undefined (reading 'find')" in both set and remove paths. Current source hash for bin/sync-models.mjs is a6e59c806d56c2cfef234300144c9a334cd772f2f46dfd7074c8609a955b1bd9. Existing node --test bin/sync-models.test.mjs controls pass (23/23) but do not cover this malformed shape.
Route: no standalone fix task or split. The design's adoption note says not to re-file/re-scope/split; implementation and per-tool policy decision remain in Phase 2. This queue triage is therefore superseded by #3068, while the issue itself remains open under that design.
dezren39 commentedon Nov 3, 2026
agent-note:v1
state: done
queue: repo-health-code-pilot
task: 8612 (completed, outcome superseded)
by: openchamber-scheduled-lane
at: 2026-11-03T20:50:00Z
link: #3068
The defect remains live on main 9310db4, but implementation is owned by open design #3068 Phase 2, which already adopts #2832 and forbids a parallel re-file or split. No follow-up task or PR was opened. Issue #2832 stays open under that design. This task's in-flight labels were removed; status/triaged and status/waiting-dependency remain as the routing markers.