Skip to content

fix(ci): use exact-name match for required check-runs (no regex)#54

Merged
StefanSteiner merged 1 commit into
tableau:mainfrom
StefanSteiner:ssteiner/fix-jq-required-regex-escaping
May 27, 2026
Merged

fix(ci): use exact-name match for required check-runs (no regex)#54
StefanSteiner merged 1 commit into
tableau:mainfrom
StefanSteiner:ssteiner/fix-jq-required-regex-escaping

Conversation

@StefanSteiner
Copy link
Copy Markdown
Contributor

Summary

Both release.yml's verify job and npm-build-publish.yml's verify-ci job poll GitHub's check-runs API for a canonical list of required CI checks, then gate the release/publish on all of them being green. They built that list as a regex-shaped string and fed it to jq's test(). The regex contained \(, \), and \+ to escape parens and a literal plus in check-run names like test (ubuntu-latest) and hyperdb-api-node (build + smoke).

The escapes survive bash's single quotes and the double-quoted --jq argument unchanged, so jq receives ... test \(ubuntu-latest\) ... — and jq parses \ as the start of an unrecognized escape sequence. Both publish workflows fail in ~10 seconds with:

failed to parse jq expression (line 1, column 133)
    [.check_runs[] | select(.name | test("...test \(ubuntu-latest\)..."))]
                                                ^  unexpected token "\"

This surfaced when I tried to release v0.2.2: the manually-created GitHub Release fired both release.yml and npm-build-publish.yml, both crashed in the verify-ci step, and no crates.io or npm publish happened. v0.2.0 and v0.2.1 had hit the same bug — past releases that did make it out went via different paths (manually-published tarballs / hand-edited workflows during the release-pipeline rebuild).

Fix

Drop the regex entirely. Use exact-name set membership against a JSON array of required check-run names:

REQUIRED_JSON='[
  "rustfmt","clippy","cargo-audit","cargo-deny",
  "version consistency","publish dry-run",
  "test (ubuntu-latest)","test (macos-14)","test (windows-latest)",
  "hyperdb-api-node (build + smoke)"
]'
RUNS=$(gh api "..." --jq "[.check_runs[] | select(.name as \$n | $REQUIRED_JSON | index(\$n))]")

jq's index() does ordinary string equality — no escaping, no regex, no Oniguruma to argue with. Names with parens and pluses match verbatim. Same shape applied to both workflows so they stay parallel.

Test plan

  • CI green on the PR.
  • After merge, the next release-please cycle (currently in flight for the next patch — 0.2.3 will probably be triggered by this fix: commit) runs the verify-ci step and emits an Attempt 1/30: total=10 completed=10 failed=0 line, then CI passed (10 required check-runs completed successfully). instead of the jq parse error.
  • Both release.yml and npm-build-publish.yml reach their actual publish steps.

Followups

  • v0.2.2 didn't actually publish to crates.io or npm. Once this PR merges and the next release rolls (which would normally be 0.2.3 from this commit), we should confirm via npm view hyperdb-mcp-darwin-arm64 versions --json and cargo search hyperdb-mcp that the v0.2.2 tag's binaries finally make it out (or whether we need to manually re-trigger the v0.2.2 publish workflows by re-publishing the GitHub Release).
  • Release-please's autorelease: pending label drift is a separate recurring problem (memory note: project_release_please_label_check.md). Whenever a release-please run completes successfully but no release PR appears, the prior release PR is stuck on autorelease: pending. Worth a separate hardening PR.

Both `release.yml`'s `verify` job and `npm-build-publish.yml`'s
`verify-ci` job used jq's `test()` against a regex-shaped `REQUIRED`
variable to pick out the canonical CI check-runs to wait on. The
regex contained `\(`, `\)`, and `\+` to escape parens and a plus
inside the names like `test (ubuntu-latest)` and
`hyperdb-api-node (build + smoke)`.

Bash strips one layer of backslash from a single-quoted string... no,
wait — single quotes preserve backslashes literally. The actual
breakage chain is:

  bash:  REQUIRED='...test \(ubuntu-latest\)...'
         (single quotes preserve `\(` literally)
  bash:  --jq "[... select(.name | test(\"$REQUIRED\"))]"
         (double quotes preserve `\(` again)
  jq:    receives `... test ( ... ` containing `\(`
         and parses the `\` as the start of an escape sequence
         which is not valid jq syntax.

Result: every release.yml and npm-build-publish.yml run that hit the
verify-ci step exited immediately with:

  failed to parse jq expression (line 1, column 133)
      [.check_runs[] | select(.name | test("...test \(ubuntu-latest\)..."))]
                                                  ^  unexpected token "\\"

The v0.2.2 release surfaced this — both publish workflows failed in
9-11 seconds and no crates.io / npm publish happened.

Fix: drop the regex entirely. Use exact-name set membership against a
JSON array of required check-run names. jq's `index()` handles
ordinary string equality with no escaping. Names like
`test (ubuntu-latest)` and `hyperdb-api-node (build + smoke)` are
matched verbatim — no parens, no plus, no backslashes anywhere.
@StefanSteiner StefanSteiner merged commit fc13637 into tableau:main May 27, 2026
11 checks passed
This was referenced May 27, 2026
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.

1 participant