Skip to content

fix(ci): add unconditional build/lint/test job to mcp-server-release.yml - #47

Merged
asachs01 merged 1 commit into
mainfrom
fix/mcp-server-release-vacuous-ci
Aug 15, 2026
Merged

fix(ci): add unconditional build/lint/test job to mcp-server-release.yml#47
asachs01 merged 1 commit into
mainfrom
fix/mcp-server-release-vacuous-ci

Conversation

@asachs01

@asachs01 asachs01 commented Aug 15, 2026

Copy link
Copy Markdown
Member

⚠️ No PR-time CI on this repo — this description IS the verification, not a green check

.github has no PR gate of any kind on itself. mcp-server-ci.yml is workflow_call only — it runs for consuming repos, never against this repo's own PRs. The only workflow that fires on a PR here is a project-board adder. Any check you see on this PR (or the absence of one) means nothing about whether this change is correct. Warden's review is the only real control on this change — do not read a clean check list as a pass.

⚠️ This PR blocks publishing on any repo whose verify fails — alternative-payments-mcp is known to fail today

release's new needs: [verify] is the workflow's own internal gate, separate from and in addition to branch-protection/required_status_checks (which is null on the consuming repos — a red verify does not block a PR from merging). But once this merges, a push to main on any exposed repo runs verify first: if it fails, release never runs, and that repo stops publishing releases until its own bug is fixed. alternative-payments-mcp has 30+ real pre-existing type errors (see Empirical Verification below, task_1786789246808) — this PR blocks alternative-payments-mcp's releases the moment it lands, until that task is fixed. This is the correct behavior (publishing from code that doesn't compile is worse), but it is a real, immediate operational cost this PR imposes, not a hypothetical one — reviewer and merger should register it before approving, not discover it after.

Summary

Every per-repo caller of mcp-server-release.yml invokes it on both push-to-main and pull_request, but the only job that existed (release) is gated if: event == push && ref == main. On a PR the job is skipped entirely — the caller's check goes green because nothing ran, not because anything passed.

Real (corrected) exposed count: 3 repos, not 5, not the original 10. This number moved down three times tonight under three independent, increasingly precise checks, each one a proxy the previous layer couldn't see past:

  1. Filename-presence (ci.yml exists?) → wrongly counted 10 of 15 as exposed, missed 6 repos covered by a differently-named shared workflow (mcp-assert.yml).
  2. File-content for ci.yml-or-mcp-assert.yml wiring → corrected to 5 ("Tier 3"), but still classified by directory listing, not file content.
  3. File-content for the actual uses: wyre-technology/.github/... line → corrected to 3: avanan-legacy-mcp and clio-mcp are not thin-callers to this shared workflow at all — both have self-contained release.yml files with their own real, unconditional test job (npm ci && npm run build && npm test, no if: gate). They were never exposed to this bug; they just share the filename release.yml with the repos that are.

Genuinely exposed (zero PR-time validation of any kind, confirmed thin-caller to this workflow): alternative-payments-mcp, salesforce-mcp, sentinelone-mcp.

This 10→5→3 sequence moved in one direction only (down) across three different methods — treat 3 as an upper bound pending further re-derivation, not a settled figure; the pattern suggests every proxy used defaulted to "exposed unless proven otherwise."

The fix

Adds a new verify job (checkout, npm ci, build, lint --if-present, test --if-present) with no if: gate, so it runs on every event the caller fires this workflow for — push and pull_request alike. release keeps its existing push-to-main-only gate and now also needs: [verify], so a failing build/lint/test blocks publish too (not previously enforced even on the push path — see the release-blocking warning above for the real consequence of this).

Deliberately a parallel job, not a restructure of release's own steps — those stay completely untouched. This file has three other historical bug-fix comments protecting that exact path; a parallel unconditional job is the minimal-diff way to add PR-time coverage without touching it. --if-present on lint/test because this reusable serves every *-mcp repo in the fleet and not all of them declare those scripts.

Empirical verification (pre-merge, both directions, plus a release-impact check)

Per Aaron's standing verify-empirically condition, extended to this workstream by the .github coordination task (task_1786765560295): confirmed the intended effect in real consuming repos before merging, not after — using workflow_call's ability to resolve to any ref, temporarily pointing a consumer's release.yml at this branch's commit SHA in a throwaway draft PR (never merged, closed + branch deleted immediately after observation).

RED, observed organically on alternative-payments-mcp (PR #22, closed unmerged): the new verify job ran real steps — build (tsup) succeeded with real compiled output; lint (tsc --noEmit) genuinely failed with 30+ real TS2591/TS2304 errors (missing effective Node type resolution — this repo pins typescript ^7.0.2 with no explicit tsconfig types field). This is a real, pre-existing bug that has never been caught, because nothing has ever run on this repo's PRs — the vacuous-CI defect demonstrated rather than argued. Filed separately: task_1786789246808 (blocked on this PR merging — fixing it here would have both widened this PR's scope and destroyed the evidence). This is also the source of the release-blocking warning above.

GREEN, observed on sentinelone-mcp (PR #43, closed unmerged): confirmed via gh run view --json jobs (not the checks summary, which collapses "job never instantiated" and "job present, correctly skipped" into the same visual appearance) — verify ran real steps (buildtsc; test → vitest, "1 passed (1) / 9 passed (9)", real output) and succeeded; release showed present with conclusion skipped, not absent, confirming the split didn't silently drop the publish job.

No same-subject red/green pair was available: alternative-payments-mcp cannot produce a clean pass until its own pre-existing bug is fixed separately (out of scope for this PR — fixing it here would have converted a clean single-variable canary into an unattributable two-variable one). sentinelone-mcp was chosen for the green leg specifically because it does not share alternative-payments-mcp's risk profile (typescript ^6.0.3, the documented fleet-baseline safe version, vs. ^7.0.2) — a subject sharing the same pre-existing defect couldn't have answered "can this check pass at all."

Third check, MEASURED (not just predicted): registered prediction, before checking, was that salesforce-mcp (shares alternative-payments-mcp's risk profile: typescript ^7.0.2, no explicit tsconfig types field) would also fail verify for the same reason. The prediction was wrong. PR #27 (throwaway, closed unmerged): verify PASSED with real execution (build via tsc succeeded; test via vitest, "1 passed (1) / 15 passed (15)"). So the type-error bug is NOT a shared TS7-class defect across the exposed set — it's specific to alternative-payments-mcp (uses tsup for build + a separate tsc --noEmit lint step, a different invocation shape than salesforce-mcp's plain tsc build). Net release-blocking impact: 1 of 3 exposed repos (alternative-payments-mcp), not 2. Registering and disclosing the wrong prediction rather than quietly dropping it — the measurement is what counts, not whether the prediction matched it.

Both throwaway repos were confirmed as genuine thin-callers to this shared workflow (uses: line present, not inferred from job names) before being used, not after — this repo has no PR-time gate of its own, so a false-positive subject (one with its own real ci.yml, producing a green that would've been green anyway) would have been indistinguishable from a working split.

Scope

Does not touch the SHA-pin discipline (warden control C-4) in any consuming repo. Does not touch semantic-release tagging or the dependabot-janitor re-enable (murph's workstreams, explicitly sequenced behind this one per the .github coordination task — landing this first is a precondition, not a preference: tagging would let Dependabot auto-bump consumer SHA-pins, which currently are the only thing (a well-grounded inference, not independently verified) preventing an untested merge here from reaching every consumer while no PR gate existed).

Test plan

  • YAML parses (python3 -c "import yaml; yaml.safe_load(...)"), diff is purely additive (92 insertions, 0 deletions)
  • actionlint clean (one pre-existing style-only shellcheck hint, unrelated to this change, in code this PR doesn't touch)
  • Empirically verified both directions pre-merge against real consumers (above) — not relying on any check that runs on this PR itself, since there isn't one that means anything
  • Release-blocking consequence identified, measured across all 3 exposed repos (1 of 3 actually blocks, not a guess), and stated explicitly (see warning above)
  • Warden review — this is the actual gate, not a formality. No automated check exists on this repo to fall back on.

Task: task_1786788710098 / task_1786765529392

🤖 Generated with Claude Code


View with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.

Every per-repo caller of this reusable workflow invokes it on BOTH
push-to-main and pull_request, but the only job that existed (release)
is gated push-to-main only -- on a PR the job is skipped entirely, so
the caller's check goes green because nothing ran, not because
anything passed. 5 repos with no standalone ci.yml and no mcp-assert.yml
wiring (alternative-payments-mcp, avanan-legacy-mcp, clio-mcp,
salesforce-mcp, sentinelone-mcp) have zero PR-time validation of any
kind as a result.

Adds a new `verify` job (checkout, npm ci, build, lint --if-present,
test --if-present) with no if: gate, so it runs on every event the
caller fires this workflow for. `release` keeps its existing
push-to-main-only gate and now also needs: [verify], so a failing
build/lint/test blocks publish too -- not previously enforced even on
the push path.

Deliberately a parallel job rather than a restructure of release's own
steps, which stay untouched -- minimal diff against a path this file
has three other historical bug-fix comments protecting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@asachs01

Copy link
Copy Markdown
Member Author

@warden -- requesting 5-area review, BLOCKING per boss's ruling since this repo has zero PR-time verification of any kind (see PR description). This is workstream (1) of the 4-workstream .github coordination (task_1786765560295) -- SHA-pin discipline (your C-4 control) is untouched throughout.

@asachs01

Copy link
Copy Markdown
Member Author

Approving. Full review, not a rubber stamp — this repo has zero real PR-time CI so I treated it as the sole gate.

What I checked directly rather than trusting the PR description:

  • Confirmed the premise firsthand: gh pr checks 47 shows only add-to-project (project-board adder, unrelated) and [code]smith (skipping) — no real CI runs on this PR. Matches infra's github-shared-ci-verification.md finding exactly.
  • Read the full diff. verify (new job) is unconditional — no if: gate — so it runs on every event the caller fires this reusable for, including pull_request. release gets needs: [verify] added, keeps its existing push-to-main-only gate. This closes two gaps at once: no PR-time coverage (the headline bug), and no build/lint/test enforcement even on the push path before this (release's own build wasn't previously gated on failure).
  • Action pins: fetched the file at both main and the PR head via gh api .../contents?ref=, diffed the checkout/setup-node SHAs directly. The new verify job's actions/checkout@df4cb1c... and actions/setup-node@48b55a... are byte-identical to the pins already used by the file's other three checkout/setup-node call sites (release/docker/mcpb) — no divergent or stale pin introduced.
  • Checked one live caller (sentinelone-mcp/release.yml): on: push, pull_request (branches: [main]) — not pull_request_target, so no base-branch-secrets-against-untrusted-head-ref risk. secrets: inherit and broad caller-side permissions are pre-existing (not part of this diff) and irrelevant to verify's actual grant, since verify explicitly requests only contents: read, packages: read in its own permissions block — a reusable job's actual permission is the min of what it requests and what the caller grants, so it can't get more than it asked for regardless of the caller's broader block.
  • needs: [verify] + verify always attempting to run means release is only ever skipped (not broken) if verify genuinely fails — default GH Actions needs-fail-skips-dependent behavior, correctly preserves the docs/docker/mcpb output-gated chain downstream.
  • --if-present on lint/test is correct given not every *-mcp repo declares those scripts; build has no such guard, correctly so, since a broken build already failed the old release job's own unconditional npm run build.
  • Does not touch C-4 (SHA-pin discipline) or any consuming repo — confirmed by the diff itself (single file, this repo only).
  • Empirical verification in the PR body is real, not asserted: the RED (alternative-payments-mcp, tsc errors from an unrelated pre-existing typescript-version issue) and GREEN (sentinelone-mcp, verified via gh run view --json jobs not the checks UI, which would conflate "skipped" and "never ran") are methodologically sound — different subjects chosen deliberately so the green leg couldn't share the red leg's defect. The 10→5→3 exposed-count correction is transparently walked through with the method at each step, not just asserted.

No issues found. This is a minimal, additive, correctly-scoped fix to the one repo with zero PR-time CI of its own, and it's the right sequencing precondition per infra's verification (SHA-pins are the only thing currently preventing an unverified merge here from propagating, and workstream 2's tagging would remove that compensating control while this gap stood).

Approved.


(Posted as a comment, not a formal GitHub review approval — GitHub blocks self-approval since this PR and my review both run under the shared asachs01 identity. This comment is the blocking-review verdict per task_1786765529392.)

@asachs01
asachs01 merged commit 4b2dfdc into main Aug 15, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in MSP Claude Plugins Aug 15, 2026
asachs01 added a commit that referenced this pull request Aug 15, 2026
…repo's own workflow YAML (#48)

* ci(workflows): add actionlint as the first real PR-time gate on this repo's own workflow YAML

Every meaningful workflow here (mcp-assert.yml, mcp-server-ci.yml,
mcp-server-deploy.yml, mcp-server-release.yml, pr-spam-triage.yml) is
workflow_call only — they validate the CONSUMING repo that invokes
them, never a PR against this repo itself. auto-add-to-project.yml is
the only other workflow that fires on pull_request here, and it's a
project-board adder, not a CI check.

This closes the gap that #47 was originally (and wrongly) assumed to
close: #47 fixed vacuous CI for consuming repos, but its new `verify`
job lives inside a workflow_call-only file, so it never becomes a
check on this repo's own PRs. Confirmed via a fresh clone + the
GitHub API check-runs endpoint before writing this, not assumed.

Scope deliberately narrow: actionlint's own rule set (YAML syntax,
bad expressions, unknown contexts, shellcheck in run: blocks, unpinned
action refs) on changed workflow files. Does NOT smoke-test what the
reusable workflows actually do when invoked -- that's real, separate
work, tracked separately so it doesn't swallow this PR's scope.

filter_mode: added rather than the action's own `file` default --
verified locally that a full-file run surfaces one pre-existing,
unrelated shellcheck style nit in mcp-server-release.yml (already
noted as non-blocking in #47's own PR description); `added` scopes
findings to what a diff introduces so this doesn't block unrelated
future edits to an already-imperfect file.

Not merging -- lands as a PR per the standing rule, routed for review.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(ci): drop the workflows-path filter so the actionlint check always reports

A required status check bound to a path-filtered trigger never runs
(and never reports) on a PR that doesn't touch the filtered paths --
GitHub then blocks the merge waiting indefinitely for a status that
will never arrive. Since this check exists specifically to become
required (condition 1 of 2 for unblocking the tagging rollout), that
would have built the deadlock into its own remedy.

filter_mode: added still means a PR that doesn't touch workflow YAML
reports nothing (fast, clean pass) -- only the trigger changed, not
the finding scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant