Skip to content

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

Open

Description

@dezren39

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.
  • setJsoncPath with "google": "oops" → TypeError: Cannot read properties of undefined (reading 'find') — setJsoncPath walks object = member.value (:391) into the scalar/array tree node, whose members is undefined, and object.members.find (:385 / :365) throws.
  • Same throw with "google": [1, 2] (array node also has no members) and with "models": "oops" as the final intermediate.
  • removeJsoncPath through a scalar or array intermediate → same TypeError (:401/:404).
  • run()-level shapes, against real parseJsonc output: a config with no provider key hits config.provider['openai-codex'] (:559, and the antigravity block at :496 the same way) → TypeError: Cannot read properties of undefined (reading 'openai-codex').
  • "models": "oops" survives prov.models = prov.models || {} (:498/:561 — a non-empty string is truthy) and the first prov.models[m] = … assignment throws TypeError: 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.jsonc produces a named, actionable error (config path + the offending key path, e.g. provider.google: expected object, got string) — never a bare Cannot read properties of undefined / Cannot create property TypeError.
  • Triage pins the treatment per site: fail-fast with the named error for the provider/models containers 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 in run() and in the tree editor.
  • setJsoncPath / removeJsoncPath refuse a non-object intermediate node with an error naming the key path instead of dereferencing members on it.
  • A missing provider key is treated as "nothing to sync" (or the named error above), not a crash.
  • Tests in bin/sync-models.test.mjs pin: scalar provider entry, array provider entry, scalar models, missing provider key — 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

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/sync-modelsbin/sync-models.mjs model catalog sync
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-code-a)
    • Session / ID: new-issue-creator-code-a run 2026-10-30 16:05 CDT
    • Found via: filed this run
    • Task: Triage the parseable-but-wrong-shaped opencode.jsonc crashes in bin/sync-models.mjs and pin the treatment per site (fail-fast named error vs skip-and-warn), covering run()'s provider/models derefs and the JSONC tree editor's non-object intermediates
    • Expectation: effort/xs — done when wrong-shaped provider/models values produce a named error (config path + key path) or a pinned skip, setJsoncPath/removeJsoncPath refuse non-object intermediates by key path, a missing provider key no longer crashes, and bin/sync-models.test.mjs pins scalar/array provider, scalar models, and missing provider
    • Links: evidence in the issue body; probe on laptop ~/git/fleet/probe-2026-10-30-laneA-syncshape2/ (verbatim extraction from the real sync-models.mjs, md5-matched to main 3a3d59b)
  3. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 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).

  4. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  5. 34 remaining items

  6. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 3, 2026
  7. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    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 main before completing.

  8. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    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 main 821c02105 (bin/sync-models.mjs md5 f1d1ea88 — unchanged since the 22892c316 verification). Verbatim extraction of parseJsonc / parseJsoncTree / setJsoncPath / removeJsoncPath from the real module, same method as bin/sync-models.test.mjs:

    • CONTROL set well-formed → OK (prior passes); CONTROL remove well-formed → OK.
    • setJsoncPath with "google": "oops" → TypeError: Cannot read properties of undefined (reading 'find')
    • setJsoncPath with "google": [1, 2] → same unnamed TypeError
    • removeJsoncPath through a scalar intermediate → same unnamed TypeError
    • run()-level sites unchanged: prov.models = prov.models || {} at :541 still passes truthy non-objects through to a later Cannot create property TypeError; config.provider[targetProvider] derefs still assume a provider object.

    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/queued work order so coverage stops minting no-op triages; set status/waiting-dependency (#3068 Phase 2) so the work order lives with the design. When Phase 2 lands the fix, status/waiting-dependency clears 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-dependency is the marker that this issue is not waiting on a queue worker.

  9. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    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/triaged retained as the routing marker.

  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off (re-handoff)

    • Agent: queue worker (repo-worker-code), Lane J shift (code-j)
    • Session / ID: task 8612 (triage, repo-health-code-pilot), attempt 3
    • Found via: queue claim
    • Task: Re-handed off to the opencode execution lane (lane row for task 8612, assigned_lane=opencode, session still unbound at handoff). Resume context for the bound session, from the saved checkpoint: the defect is already triage-verified live — parseable-but-wrong-shaped opencode.jsonc throws unnamed TypeErrors in 4 wrong-shape tree-editor cases + 2 run()-level cases, controls green. Remaining: finish the triage terminal work from that finding (verdict comment if not yet posted in full, scoped fix enqueue, complete as triaged) rather than re-deriving it.
    • Expectation: this lane does no triage work itself; claim labels already synced (work/assigned, queue/claimed, status/in-progress); the issue also carries status/triaged and status/waiting-dependency from a prior lane — left untouched for the bound session/monitor to reconcile. No hard-stop labels.
  11. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  12. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    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.

  13. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    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.

  14. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/sync-modelsbin/sync-models.mjs model catalog synceffort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobstatus/triagedTriaged and routed (see agent note)status/waiting-dependencyWaiting on a dependencytask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions