Skip to content

fix(core): parse exec form the way Docker does - #96

Merged
PunGrumpy merged 4 commits into
mainfrom
fix/issue-89-parse-exec-form
Aug 24, 2026
Merged

fix(core): parse exec form the way Docker does#96
PunGrumpy merged 4 commits into
mainfrom
fix/issue-89-parse-exec-form

Conversation

@PunGrumpy

Copy link
Copy Markdown
Owner

Description

use-exec-form decided exec form with args.startsWith("[") && args.endsWith("]"), so anything wearing brackets passed:

CMD [node, index.js]                  # unquoted tokens
ENTRYPOINT ['docker-entrypoint.sh']   # single quotes
CMD ["node", "index.js",]             # trailing comma

None of those are JSON, so Docker runs them under /bin/sh -c and signals never reach the process — the exact failure the rule exists to catch. All three reported 0 diagnostics; they now report 1.

What changes

parseExecForm(args): string[] | null lands in parsers/, backed by JSON.parse plus an array-of-strings check, and returns the argv rather than a boolean so callers don't parse twice. use-exec-form routes its check through it.

use-pipefail (from #86) carried two helpers of its own — isExecForm and jsonArrayEnablesPipefail — that were looser than use-exec-form's check, so the two rules could disagree about what a line even is. Both now go through the shared parser. That commit is a pure refactor: SHELL and exec-form RUN keep their existing diagnostics.

An array holding a non-string element (CMD [1, 2]) returns null as well. Docker rejects it outright instead of falling back to shell form, so the Dockerfile is broken either way and a shell-form diagnostic still points at the offending line.

This surfaces new warnings on Dockerfiles that previously passed. Quoting the elements (CMD ["node", "index.js"]) resolves them, which is what the rule was always asking for.

Related Issues

Closes #89

Checklist

  • I've reviewed my code
  • I've written tests
  • I've generated a change set file
  • I've updated the docs, if necessary — the use-exec-form page already documents JSON-array syntax; only detection changed

Screenshots (if applicable)

N/A — no UI surface.

Additional Notes

  • Four atomic commits: the parser (with its own unit tests), the use-exec-form fix, the use-pipefail refactor, then the changeset. Each is independently checked out and green.
  • parseExecForm has direct unit coverage for every spelling above plus whitespace, empty arrays, non-array JSON, and unterminated input; rules.test.ts covers the three reproductions end to end through the rule.
  • Heads up, unrelated to this branch: running raw bun test from the repo root fails workflow-scaffold > creates the workflow under the scanned root, not the cwd. Its assertion checks that .github/workflows/docker-doctor.yml is absent from process.cwd(), which the repo root legitimately owns. bun run test (what CI runs) passes because it runs per package. Reproduces on 8105153, before this branch.

Docker only treats bracket-wrapped instruction args as exec form when
they parse as a JSON array of strings; `CMD [node, index.js]` and
`ENTRYPOINT ['x']` fall back to shell form under /bin/sh -c. Two rules
were each deciding that for themselves with a different, looser test.

parseExecForm returns the argv or null, so callers get the parsed
command back instead of re-parsing after a boolean check. An array
holding a non-string element is null as well: Docker rejects it
outright, so the line is broken either way and a shell-form diagnostic
still points at it.
use-exec-form accepted anything starting with [ and ending with ], so
`CMD [node, index.js]`, `ENTRYPOINT ['docker-entrypoint.sh']` and a
trailing comma all passed as exec form. Docker parses none of them as
JSON, runs them under /bin/sh -c, and drops signals on the way to the
process -- the exact failure the rule exists to catch.

Route the check through parseExecForm so the rule agrees with Docker.
Dockerfiles that relied on the looser check will now see a warning;
quoting the elements resolves it.

Closes #89
use-pipefail carried its own isExecForm and jsonArrayEnablesPipefail,
both looser than use-exec-form's check: an array of non-strings counted
as exec form here but not there, so the two rules could disagree about
what a line even is.

parseExecForm returns the argv, which removes the parse-then-reparse
both helpers did -- isExecForm parsed the args only to throw the result
away, then jsonArrayEnablesPipefail parsed the same string again.
Behavior is unchanged; SHELL and exec-form RUN keep their existing
diagnostics.
@changeset-bot

changeset-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0b36d74

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@docker-doctor/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Aug 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docker-doctor Ready Ready Preview Aug 24, 2026 2:47pm

@github-actions

Copy link
Copy Markdown
Contributor

The latest Docker Doctor scan for this pull request. Learn more about Docker Doctor.

File Status Issues Updated (UTC)
Dockerfile Error 1 error, 2 info Aug 24, 2026 2:47pm

Score: 84 / 100 · Good

3 issues

Dockerfile

  • error Dockerfile:2 Potential secret found in ENV: 'DB_PASSWORD'. no-secrets-in-env
  • info Dockerfile:1 No HEALTHCHECK instruction found. require-healthcheck
  • info Dockerfile:1 No LABEL metadata was found in this Dockerfile. require-labels

Scanned by Docker Doctor for commit 0b36d74.

@PunGrumpy
PunGrumpy merged commit 1cda220 into main Aug 24, 2026
13 checks passed
@PunGrumpy
PunGrumpy deleted the fix/issue-89-parse-exec-form branch August 24, 2026 17:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(core): use-exec-form misses bracket-wrapped args that are not valid JSON

1 participant