repocfg: accept array form for command run #51

Open
opened 2026-05-23 20:54:02 +00:00 by coilysiren · 0 comments
Owner

Originally filed by @coilysiren on 2026-05-09T06:16:12Z - https://github.com/coilysiren/coily/issues/106

The run key in .coily/coily.yaml is currently a string that gets split via strings.Fields at load time:

commands:
  test:
    run: go test ./...

Whitespace-split papers over the fact that the post-split tokens are what actually feed into argv. The string form is a leaky abstraction that looks like a shell command but isn't one (no quoting, no metacharacters, no expansion). Replace it with an explicit argv array as a breaking migration.

Target shape

commands:
  test:
    run:
      - go
      - test
      - ./...
  test-with-spaced-arg:
    run: [go, test, -run, "TestFoo And Bar"]

policy.ValidateArg runs on each token, same rule as today. The shorthand <name>: <command line> (string scalar in place of the mapping) is also retired.

Breaking-migration plan

This is a breaking change. Scalar run and the string-scalar shorthand both hard-fail at load time once the new schema ships. No deprecation window, no auto-split fallback. The whole point of the migration is that "looks like a shell command, isn't one" is the bug; preserving the scalar path while introducing the array path keeps the bug.

Rollout sequence:

  1. Land the parser change in coily. Scalar run returns a clear error pointing at the offending file, key, and the array form.
  2. Migration script in scripts/ that walks coilysiren/* and rewrites every .coily/coily.yaml from scalar to array form. Mechanical, idempotent, no judgment calls.
  3. Run the migration across the 19 known files (per docs/repocfg-schema.md), one commit per repo.
  4. Cut a coily release. From the next brew upgrade forward, any unmigrated repo fails fast with the new error.

Why a breaking migration vs additive

  • The string form's contract ("we'll whitespace-split for you") is exactly the leak this fixes. Keeping both paths means future readers still have to reason about "which form does this repo use, and does it matter."
  • Mechanical migration of 19 files is cheap. The cost of the breaking change is bounded and auditable.
  • Out-of-tree consumers (none today) would surface immediately on the next upgrade with a clear error, not silently keep the old behavior.

Why now

  • docs/repocfg-schema.md just landed and describes the de facto schema (#92 closed).
  • #105 wires up known-fields enforcement for unknown keys. Stacking the run-form change on the same enforcement pass keeps the schema churn in one window.
  • Argv-shaped from the start matches every adjacent tool (GitHub Actions args, docker CMD, k8s command/args).

Out of scope

  • Mixed forms or per-command opt-in. The breaking migration is the whole point.
  • Shell-pipeline support. Pipes still live in a checked-in script the command exec's, not inline.

Adjacent

Companion to #105 (schema enforcement) and docs/repocfg-schema.md. Land #105 first so the new error infrastructure (file path + key + line number) is available; this issue reuses it.

_Originally filed by @coilysiren on 2026-05-09T06:16:12Z - [https://github.com/coilysiren/coily/issues/106](https://github.com/coilysiren/coily/issues/106)_ The `run` key in `.coily/coily.yaml` is currently a string that gets split via `strings.Fields` at load time: ```yaml commands: test: run: go test ./... ``` Whitespace-split papers over the fact that the post-split tokens are what actually feed into argv. The string form is a leaky abstraction that looks like a shell command but isn't one (no quoting, no metacharacters, no expansion). Replace it with an explicit argv array as a breaking migration. ## Target shape ```yaml commands: test: run: - go - test - ./... test-with-spaced-arg: run: [go, test, -run, "TestFoo And Bar"] ``` `policy.ValidateArg` runs on each token, same rule as today. The shorthand `<name>: <command line>` (string scalar in place of the mapping) is also retired. ## Breaking-migration plan This is a breaking change. Scalar `run` and the string-scalar shorthand both hard-fail at load time once the new schema ships. No deprecation window, no auto-split fallback. The whole point of the migration is that \"looks like a shell command, isn't one\" is the bug; preserving the scalar path while introducing the array path keeps the bug. Rollout sequence: 1. Land the parser change in coily. Scalar `run` returns a clear error pointing at the offending file, key, and the array form. 2. Migration script in `scripts/` that walks `coilysiren/*` and rewrites every `.coily/coily.yaml` from scalar to array form. Mechanical, idempotent, no judgment calls. 3. Run the migration across the 19 known files (per `docs/repocfg-schema.md`), one commit per repo. 4. Cut a coily release. From the next `brew upgrade` forward, any unmigrated repo fails fast with the new error. ## Why a breaking migration vs additive - The string form's contract (\"we'll whitespace-split for you\") is exactly the leak this fixes. Keeping both paths means future readers still have to reason about \"which form does this repo use, and does it matter.\" - Mechanical migration of 19 files is cheap. The cost of the breaking change is bounded and auditable. - Out-of-tree consumers (none today) would surface immediately on the next upgrade with a clear error, not silently keep the old behavior. ## Why now - `docs/repocfg-schema.md` just landed and describes the de facto schema (#92 closed). - #105 wires up known-fields enforcement for unknown keys. Stacking the run-form change on the same enforcement pass keeps the schema churn in one window. - Argv-shaped from the start matches every adjacent tool (GitHub Actions args, docker CMD, k8s command/args). ## Out of scope - Mixed forms or per-command opt-in. The breaking migration is the whole point. - Shell-pipeline support. Pipes still live in a checked-in script the command exec's, not inline. ## Adjacent Companion to #105 (schema enforcement) and `docs/repocfg-schema.md`. Land #105 first so the new error infrastructure (file path + key + line number) is available; this issue reuses it.
coilysiren added
P4
and removed
P3
labels 2026-05-31 06:59:48 +00:00
Sign in to join this conversation.
No labels
P0
P1
P2
P3
P4
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-bridge/coily#51
No description provided.