Skip to content

app validate passes an empty package-lock.json — a green check for a build that cannot succeed #255

Description

@ZacxDev

Found by a blind dogfood run against v0.1.90-21-gf56aa72 (agent given only the built binary, README.md and --help; no source, no credential).

Symptom

civitai app validate reports ✓ is valid, exit 0, for a project whose committed package-lock.json is empty. The platform build will still fail.

$ civitai app create dogcheck --dir ./a -y
$ civitai app validate ./a
✗ 1 validation error(s) in ./a:
  - package.json is present but no lockfile is committed — buildCommand is "npm run build",
    so the platform build will run `npm ci`, which hard-fails without package-lock.json.
    Run `npm install` and commit the package-lock.json it writes […]
rc=1

$ touch ./a/package-lock.json          # 0 bytes
$ civitai app validate ./a
✓ ./a is valid
rc=0

Why this is a real failure, not a nit

The check is presence-only — it asks whether the file exists, not whether it is a lockfile. Measured on npm 11.17.0, an empty lockfile fails npm ci with the same class of error as a missing one:

$ printf '{"name":"t","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > package.json
$ touch package-lock.json && npm ci
npm error code EUSAGE
npm error The `npm ci` command can only install with an existing package-lock.json or
npm error npm-shrinkwrap.json with lockfileVersion >= 1.

AGENTS.md item 3 states the check's entire purpose: the platform build installs strictly from the committed lockfile with no registry re-resolve fallback, and a mismatch "surfaces only as an opaque server-side build failed". That is exactly the outcome an author gets here — after validate told them they were fine.

The failure message makes touch a reachable wrong turn. It names the filename (package-lock.json) as the thing that is missing, so creating an empty one to satisfy it is a natural — and silently wrong — response. The check invites the input that defeats it.

Suggested fix

Validate that the lockfile is plausibly a lockfile, not merely present. Cheapest sufficient check: non-empty and parses as JSON and has a lockfileVersion — which is precisely what npm's own error names as the requirement (lockfileVersion >= 1). That keeps it a syntactic check (no dependency resolution, no network), consistent with the rest of validate.

Note the same question applies to the other package managers packageManagerFor recognises — pnpm-lock.yaml and yarn.lock are not JSON, so the predicate should be per-manager rather than "parse as JSON" globally. An empty file is wrong for all of them.

Test coverage this needs

Both directions, or the guard is satisfiable by a broken fix:

  • a 0-byte lockfile must FAIL validation (the regression case);
  • a real lockfile must still PASS (positive control — otherwise "reject everything" passes);
  • a lockfile with content but no lockfileVersion — decide deliberately and pin it;
  • the missing-lockfile case must keep its existing message and exit code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions