Skip to content

provider-catalog-sync: opt() takes the next token blindly — '--config --apply' opens a file named '--apply', a trailing '--config' crashes unnamed, '--models-dev --apply' is consumed twice and still writes, and a typo'd '--aply' silently becomes a dry run #3449

Closed

Description

@dezren39

Problem

bin/provider-catalog-sync.mjs parses its options with a blind next-token helper and never validates the argument list:

  • main() at bin/provider-catalog-sync.mjs:687-698 — opt(name, dflt) returns argv[idx + 1] whatever it is; the flag set is new Set(argv); no argument is ever rejected.
  • --config (:694-695), --models-dev (:696), --source-dir (:697) and --repo (in the --open-pr path) all go through opt(); --apply / --open-pr / --json are membership tests on the same argv.

Consequences, all verified below:

  1. A flag token is consumed as a value. --config --apply tries to open a file literally named --apply.
  2. A trailing value-flag crashes unnamed. --config as the last argument makes opt() return undefined, and the run dies with a raw TypeError that never mentions --config.
  3. One token is consumed twice. --models-dev --apply takes --apply as the models.dev spec (the fetch of a URL named --apply fails and the cross-check silently degrades to "unavailable") AND still honours --apply as the apply flag — so the run writes the config with the models.dev cross-check silently lost (entry limits that come from models.dev are absent), and in --apply mode no report is printed that would show the loss.
  4. Unknown arguments are silently ignored. A typo'd --aply performs a dry-run report and exits 3 (validated additions found) with the config untouched and no word that the flag was not recognised — the operator asked for an apply and got a report.

Evidence (verified this run, 2026-11-01)

Code: bin/provider-catalog-sync.mjs:687-702 (main/opt), :463 (fs.readFileSync(configPath) in runSync).

Laptop probe (sha-matched copy of the script, sha256 08e742c0cc241413f5a478040ee68174555d68c7fa34b9f3ccc6f2c53204f7cb, identical to sandbox main 98a4eb8f; probe dir ~/git/fleet/probe-2026-11-01-laneB-catalogsync/, script run.sh; fully offline: minimal config with one claude-code provider, --source-dir snapshot 18749.json advertising one new model new-model, local empty models.dev file):

  • Control dry-run → exit 3, + VALIDATED ADDITION: new-model, config untouched.
  • Control --apply → exit 0, new-model written into the config.
  • --aply (typo) → exit 3, report only, config untouched, no warning that --aply is not a recognised argument.
  • --config --apply … → exit 2, [catalog-sync] fatal: Error: ENOENT: no such file or directory, open '--apply' (runSync at :463).
  • … --config (trailing) → exit 2, TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string … Received undefined — --config is never named.
  • --config cfg --source-dir src --models-dev --apply → exit 0 and the config was written (new-model added): --apply was used both as the --models-dev value and as the apply flag; the models.dev load failed on the "URL" --apply and degraded silently.

Why it matters

This is the PR-based catalog updater for the live OpenCode config (#1297): --apply writes .opencode/opencode.jsonc in place. Its argument handling fails in both directions — a typo silently turns an apply into a report, and a misplaced flag silently turns a cross-checked apply into an un-cross-checked one that still writes. The script's own contract (dry-run exit 3 vs apply exit 0) only protects an operator whose flags parse exactly as intended, and nothing tells them when they did not.

Distinct from

Acceptance criteria

  • Any unrecognised argument aborts with a usage error and a non-zero exit before any file read, fetch, or write.
  • A value-flag (--config, --models-dev, --source-dir, --repo) followed by another flag token or by nothing aborts with a usage error naming that flag, instead of consuming the flag or crashing with a raw TypeError/ENOENT.
  • No argv token is consumed twice: --models-dev --apply is a usage error, not an apply with a silently lost cross-check.
  • --apply, --open-pr, --json and the documented value options keep working exactly as before (the probe's control cases: dry-run exit 3 with no write; --apply exit 0 with the validated addition written).
  • A stubbed/offline probe or test pins the cases above (typo'd flag, flag-as-value, trailing flag, double consumption).

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Nov 1, 2026
  2. developing-today commented on Nov 1, 2026

    @developing-today
    Contributor
  3. added
    source/creatorFiled by the new-issue-creator job
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    effort/sSmall: expected under 2 hours of work
    on Nov 1, 2026
  4. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-code-b)
    • Session / ID: new-issue-creator-code-b cron run, 2026-11-01 03:20 CST (2026-11-01T09:20Z)
    • Found via: filed this run
    • Task: Filed after verifying provider-catalog-sync's opt() argument handling with an offline laptop probe (control dry-run/apply, typo'd --aply, --config --apply, trailing --config, --models-dev --apply double consumption)
    • Expectation: effort/s — one strict argument-validation pass in bin/provider-catalog-sync.mjs plus pinned probe/test cases; done when the issue's acceptance criteria pass
    • Links: probe ~/git/fleet/probe-2026-11-01-laneB-catalogsync/ (laptop); queue packet to follow
  5. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 2, 2026
  6. 3 remaining items

  7. added
    queue/queuedHas a queued task in the Supabase agent queue
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 2, 2026
  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  9. added a commit that references this issue on Nov 2, 2026
  10. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

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

Metadata

Metadata

Assignees

Labels

app/provider-catalog-syncbin/provider-catalog-sync.mjs catalog synceffort/sSmall: expected under 2 hours 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 jobtask/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