Skip to content

chore(ci): test on windows/macos and node 22/24; pin source bytes to LF - #91

Closed
PrzemekGalarowicz wants to merge 6 commits into
mainfrom
ci-matrix-os-node
Closed

chore(ci): test on windows/macos and node 22/24; pin source bytes to LF#91
PrzemekGalarowicz wants to merge 6 commits into
mainfrom
ci-matrix-os-node

Conversation

@PrzemekGalarowicz

@PrzemekGalarowicz PrzemekGalarowicz commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Probe PR. The ground truth for a CI matrix lives on the runners, not in the repo — so this is opened as a draft to read the actual cells before deciding the final shape.

What changed

File Change
.github/workflows/ci.yml one cell → five, fail-fast: false
.gitattributes new* text=auto eol=lf
.dev/floor/check-soft-tier.mjs + .test.mjs new floor gate: no continue-on-error, anywhere

The matrix: ubuntu-latest × {20, 22, 24} + windows-latest and macos-latest × 24.

The OS-axis risk (fs, symlinks, paths, spawn) is node-version-independent and the node-axis risk (API drift) is OS-independent, so the cross-terms don't earn 9 cells. Node 24 carries the OS legs because 24 is what publish.yml ships — the release has been running a Node no test had ever seen.

Each cell runs the same six independent gates, unchanged, behind the existing always() && steps.install.outcome == 'success' guard. Both action digests are reused byte-for-byte with persist-credentials: false.

The explicit job name: is load-bearing, not decoration: without it GitHub derives each check's name from every matrix key, so adding a key later would silently rename all five required checks.

Why .gitattributes

The suite now contains tests that read the repo's own tracked bytes. This is a no-op renormalization today — zero CRLF in tracked files, and git add --renormalize . rewrites zero pre-existing files — so it costs nothing and removes the class structurally.

It is not hypothetical: check-action-pins.mjs:57-64 already carries a LINE_SPLIT_RE written specifically because "core.autocrlf on Windows produces CRLF on checkout, this repo has no .gitattributes" — a fail-open that made every uses: in a CRLF file invisible. This removes that root cause at the source instead of per-scanner.

Why the soft-tier gate

Five cells make "just mark the red one continue-on-error" a standing temptation. A yellow cell renders as present, gates nothing, and reads to every future contributor as covered. The rule existed as convention; this is the fence.

Value-blind by design: false and ${{ matrix.experimental }} are refused alongside true, so the verdict stays violations.length > 0 — an integer test — instead of depending on an untrusted file's value.

Named residuals, not claimed closed: it cannot see branch protection (R1), and it does not read shell-level swallowing like || true (R2).

⚠️ Operational note for the human — required checks

This sandbox cannot see repo settings, so the required-checks state is unverified. Both branches, verbatim:

  • If branch protection currently requires the single check job (plausible — verify in Settings → Branches): this PR renames the checks to the five per-cell names, and the required list must be updated by hand, or merges either lose the gate or wedge on a check that no longer exists.
  • If no required checks are configured: this PR is the natural moment to add them — the per-cell names are exactly what to require.

Scope

M7 is the measurement arm of the node-version-policy ticket — these matrix results are its evidence base. floor.yml's lts/* is that ticket's business and is not touched here, nor are publish.yml, engines, or node/runner pinning.

Two independent concerns land together (a measurement and an enforcement) — noted so a future bisect sees the coupling.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • CI now validates formatting, linting, tests, type checks, and builds across Ubuntu, Windows, and macOS with Node.js 20, 22, and 24.
    • Added safeguards that detect workflow steps configured to fail silently.
    • Text files are consistently normalized to LF line endings.
  • Bug Fixes

    • Blocked paths are now correctly reported as unreadable instead of missing.
  • Tests

    • Expanded automated coverage for cross-platform behavior and workflow validation.

ci.yml ran one cell — ubuntu-latest x node 20 — while publish.yml built the
shipped artifact on node 24, so the release ran a node no test had ever seen,
and Windows/macOS had never executed a single test. The untested surface was
the repo's riskiest class: safeJoin/toPosix path handling, the symlink fixtures
across apply-update/backup/install-capabilities, records byte-equality, and the
spawn-based self-tests.

Five cells now, fail-fast: false so a red cell yields a full failure table
instead of aborting its siblings: ubuntu-latest x {20, 22, 24} plus
windows-latest and macos-latest on 24 — 24 carries the OS legs because 24 is
what publish ships. Each cell runs the same six independent gates, unchanged.
The explicit job name pins check identity to os + node, so a future matrix key
cannot silently rename all five required checks.

.gitattributes (* text=auto eol=lf) makes checked-out bytes deterministic on
every OS — the suite now contains tests that read the repo's own tracked files.
It is a no-op renormalization today (zero CRLF tracked), and it removes at the
source the class check-action-pins.mjs had already been forced to defend
against per-scanner.

check-soft-tier.mjs is the fence under "no soft tier": five cells make "mark
the red one experimental" a standing temptation, and a check that renders as
present while gating nothing is worse than no check. It refuses any
continue-on-error key, value-blind, so false and ${{ matrix.experimental }} are
refused alongside true and the verdict stays an integer count rather than a
judgment about untrusted file content. Named residuals: it cannot see branch
protection, and it does not read shell-level swallowing such as `|| true`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PrzemekGalarowicz, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c647caf-e5d6-4e3d-945a-0fcb2de21080

📥 Commits

Reviewing files that changed from the base of the PR and between 76135ef and f0e43a8.

📒 Files selected for processing (1)
  • .dev/floor/check-run-pins.test.mjs
📝 Walkthrough

Walkthrough

The change expands CI to five OS and Node.js cells, normalizes text files to LF, adds a no-soft-tier workflow scanner, fixes Windows blocked-path classification, and records plan, verification, regression, review, and ship results.

Changes

CI matrix and cross-platform path handling

Layer / File(s) Summary
Matrix execution and repository policies
.dev/features/ci-matrix-os-node/PLAN.md, .github/workflows/ci.yml, .gitattributes, CHANGELOG.md
CI now runs five explicitly named OS and Node.js cells with fail-fast: false. Each gate selects the matrix Node.js version. Text files use LF normalization.
Soft-tier floor check and coverage
.dev/floor/check-soft-tier.mjs, .dev/floor/check-soft-tier.test.mjs, .github/workflows/floor.yml
The scanner detects continue-on-error in workflow and local action YAML files. Tests cover detection, enumeration, line endings, output, exit codes, and live-repository consistency. The floor workflow runs the scanner before validation.
Blocked-parent filesystem classification
src/lib/apply-update.ts, CHANGELOG.md
parentBlocks identifies blocked parent paths. readDiskState reports blocked paths as unreadable and genuinely missing paths as absent.
Verification, review, and ship evidence
.dev/features/ci-matrix-os-node/*, .pharn/*
The change adds plan, regression, verification, review, and ship records, plus updated result metadata and write scope.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant MatrixRunner
  participant SetupNode
  participant CICommands
  GitHubActions->>MatrixRunner: create five OS and Node cells
  MatrixRunner->>SetupNode: select matrix Node version
  SetupNode->>CICommands: run the six-gate job body
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main CI matrix expansion and LF normalization changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci-matrix-os-node

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

PrzemekGalarowicz and others added 2 commits August 11, 2026 20:14
The Windows CI cell went red on its first run with three failures sharing one
root cause. readDiskState used lstatSync(dest, { throwIfNoEntry: false }), whose
suppression covers ENOENT only — documented in that line's own comment as
relying on a parent-is-a-file raising ENOTDIR instead. That is a POSIX-only
truth: Windows raises ENOENT for the same situation, so it WAS suppressed and
the path was classified `absent`.

User-visible on Windows: `pharn status` listed such a path under Missing,
implying it could be restored, when a regular file squats on its parent and it
cannot exist; `pharn update` then planned that restore and exited 1 instead of
emitting the named skip. The "Unreadable" drift category has been POSIX-only
correct since it shipped.

The split no longer reads an errno. It walks the components below the project
root and observes what they ARE — segment-wise from the root, the same idiom
assertNoSymlinkPath already uses, so containment stays structural (safeJoin per
step) rather than a lexical prefix compare.

A symlinked parent is deliberately NOT blocking. It does not mean the path
cannot exist, and deciding it here would turn a planned write into a silent skip
and dissolve the ApplyError partial-failure contract — caught by two previously
green tests when the first draft got this wrong. Refusing to write through a
symlink stays applyWrites' job.

No test file is edited: the three failing tests already assert the correct
behavior, which is what makes this a product fix rather than a test
accommodation. The blocking branch is unreachable on POSIX, so the Windows cell
is now this fix's regression test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
regress no-regressions, verify PASS, review GREEN with 5 advisory findings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@PrzemekGalarowicz
PrzemekGalarowicz marked this pull request as ready for review August 12, 2026 08:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
.dev/features/ci-matrix-os-node/SHIP.md (1)

41-43: 🔒 Security & Privacy | 🔵 Trivial

Verify branch protection before merge.

The workflow now names five matrix checks instead of the previous single check job. If branch protection still requires check, merges may block. If it does not require all five new names, a merge can bypass one or more matrix cells. Confirm both protected branches require these exact checks:

  • ubuntu-latest / node 20
  • ubuntu-latest / node 22
  • ubuntu-latest / node 24
  • windows-latest / node 24
  • macos-latest / node 24

Evidence: the supplied .github/workflows/ci.yml snippet defines this name template, and this file states that repository settings are unverified.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.dev/features/ci-matrix-os-node/SHIP.md around lines 41 - 43, Verify
branch-protection settings for both protected branches and update required
status checks to exactly include the five matrix check names: ubuntu-latest /
node 20, ubuntu-latest / node 22, ubuntu-latest / node 24, windows-latest / node
24, and macos-latest / node 24. Remove any obsolete requirement for the former
check job and record the verification in the standing item.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.dev/features/ci-matrix-os-node/REVIEW.md:
- Around line 28-33: Update the R1 coverage discussion in REVIEW.md to
distinguish the existing regular-file parent regression test, which verifies
readDiskState returns unreadable without throwing, from the Windows-only
parentBlocks true branch. State that POSIX tests cover the fallback path rather
than claiming the 643 tests say nothing about R1, and retain the limitation that
the Windows branch is exercised only by the Windows CI cell.

In @.dev/floor/check-soft-tier.mjs:
- Line 75: Update SOFT_KEY_RE and the surrounding scanner to recognize explicit
YAML mapping keys written as “? continue-on-error” with the value on a following
line before “: true”. Add a regression fixture covering this multiline form and
assert that it reports a violation.
- Around line 110-116: Update the directory enumeration logic around readdirSync
so failures for existing .github/workflows and .github/actions scan roots record
an unreadable-file violation for the affected path instead of treating it as
empty. Preserve normal sorted enumeration on success, and add regression
coverage for both scan roots.

In @.dev/floor/check-soft-tier.test.mjs:
- Around line 7-9: Protect the floor workflow by adding floor to the main
protection ruleset’s required checks and enforcing the no-soft-tier scan outside
pull-request-modifiable workflow files. In
.dev/floor/check-soft-tier.test.mjs:7-9, retain the ★ test and document it as a
residual until both protections exist; update
.dev/features/ci-matrix-os-node/PLAN.md:68-77 and CHANGELOG.md:92-100 to reflect
the required floor check and external enforcement.

In @.github/workflows/ci.yml:
- Around line 13-31: Restructure the CI workflow so Format check, Lint, Markdown
lint, Typecheck, Test, and Build each run as separately named jobs rather than
steps inside the five-cell matrix job. Preserve the matrix coverage where
applicable, assign stable explicit names to every gate job, and remove the
combined check’s role as the sole required status so branch protection can
require each gate independently.

---

Nitpick comments:
In @.dev/features/ci-matrix-os-node/SHIP.md:
- Around line 41-43: Verify branch-protection settings for both protected
branches and update required status checks to exactly include the five matrix
check names: ubuntu-latest / node 20, ubuntu-latest / node 22, ubuntu-latest /
node 24, windows-latest / node 24, and macos-latest / node 24. Remove any
obsolete requirement for the former check job and record the verification in the
standing item.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f5e3d73-e85f-4f30-b09b-1bcb2d3e67ca

📥 Commits

Reviewing files that changed from the base of the PR and between 21db522 and 680efd1.

📒 Files selected for processing (18)
  • .dev/features/ci-matrix-os-node/GRILL.md
  • .dev/features/ci-matrix-os-node/PLAN.md
  • .dev/features/ci-matrix-os-node/REGRESSION.md
  • .dev/features/ci-matrix-os-node/REVIEW.md
  • .dev/features/ci-matrix-os-node/SHIP.md
  • .dev/features/ci-matrix-os-node/VERIFY.md
  • .dev/features/ci-matrix-os-node/regression-report.json
  • .dev/features/ci-matrix-os-node/verify-report.json
  • .dev/floor/check-soft-tier.mjs
  • .dev/floor/check-soft-tier.test.mjs
  • .gitattributes
  • .github/workflows/ci.yml
  • .pharn/pharn-dev-regress/base-results.json
  • .pharn/pharn-dev-regress/head-results.json
  • .pharn/pharn-dev-verify/results.json
  • .pharn/writes-scope.json
  • CHANGELOG.md
  • src/lib/apply-update.ts

Comment thread .dev/features/ci-matrix-os-node/REVIEW.md
Comment thread .dev/floor/check-soft-tier.mjs
Comment thread .dev/floor/check-soft-tier.mjs
Comment thread .dev/floor/check-soft-tier.test.mjs Outdated
Comment thread .github/workflows/ci.yml Outdated
PrzemekGalarowicz and others added 3 commits August 12, 2026 11:47
Run check-soft-tier.mjs directly in floor.yml, fail closed on unreadable
scan roots, and document the ★ live-repo tests as residual until branch
protection requires floor.

Co-authored-by: Cursor <cursoragent@cursor.com>
Pin the matrix once via a YAML anchor so every gate job fans out
identically, with explicit per-gate check names for branch protection.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.dev/features/ci-matrix-os-node/PLAN.md (3)

119-123: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use precise P5 terminology.

Line 121 says the new logic never performs classification. The soft-tier regex classifies each scanned line as a violation or non-violation. Replace this with a narrower claim, such as “does not classify values or infer semantics.”

Otherwise, the determinism audit contradicts the described key-match behavior. The supplied plan describes the regex as the violation decision.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.dev/features/ci-matrix-os-node/PLAN.md around lines 119 - 123, Revise the
P5 determinism audit in the “Determinism audit (P5)” section to avoid claiming
that the logic never performs classification. State narrowly that it does not
classify values or infer semantics, while preserving the description of
check-soft-tier.mjs using the regex key-match to decide whether each scanned
line is a violation.

113-117: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Narrow the trust-boundary claim.

check-soft-tier.mjs reads and regex-matches workflow content from the checked-out PR. Treat these files as untrusted data. The scanner does not execute workflow text, fetch remote artifacts, or make network calls. Remove the claim that “no decision reads their content.”

on: pull_request still executes fork-controlled code through npm ci and npm run. Scope the guarantee to privileged access: fork runs have contents: read, use no repository secrets, and set persist-credentials: false on each checkout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.dev/features/ci-matrix-os-node/PLAN.md around lines 113 - 117, Revise the
“Trust audit (P2)” section to treat checked-out workflow files scanned by
check-soft-tier.mjs as untrusted data, removing the claim that no decision reads
their content. State only that the scanner does not execute workflow text, fetch
remote artifacts, or make network calls, and scope the pull_request guarantee to
privileged access: contents: read, no repository secrets, and
persist-credentials: false on every checkout.

46-67: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Update the proposed ci.yml shape to match the independent gate jobs.

Lines 47-67 still document one check job with six steps. Document the six matrix jobs (format-check, lint, markdown-lint, typecheck, test, and build) with explicit gate-and-cell name: values. The current workflow emits 30 distinct gate/cell jobs; the plan must not describe the obsolete combined-job shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.dev/features/ci-matrix-os-node/PLAN.md around lines 46 - 67, Update the
proposed ci.yml shape to document six independent matrix jobs named
format-check, lint, markdown-lint, typecheck, test, and build rather than one
combined check job. Give each job explicit name values that identify both its
gate and matrix cell, and preserve the matrix OS/node combinations and required
install gating so the plan reflects all 30 emitted gate/cell jobs.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.dev/features/ci-matrix-os-node/PLAN.md:
- Around line 74-76: Update the enforcement statement in the plan to describe
the main protection ruleset’s floor requirement as intended rather than active
enforcement. At HALT 2, record that the active ruleset currently has no required
status checks, including floor, while preserving the direct floor invocation and
residual scope.

In @.github/workflows/ci.yml:
- Around line 11-20: Move the x-ci-matrix anchor from the workflow root into
format-check.strategy, replacing that job’s existing matrix alias while
preserving the fail-fast and include entries. Keep strategy: *ci-matrix
unchanged in the other five jobs.

---

Outside diff comments:
In @.dev/features/ci-matrix-os-node/PLAN.md:
- Around line 119-123: Revise the P5 determinism audit in the “Determinism audit
(P5)” section to avoid claiming that the logic never performs classification.
State narrowly that it does not classify values or infer semantics, while
preserving the description of check-soft-tier.mjs using the regex key-match to
decide whether each scanned line is a violation.
- Around line 113-117: Revise the “Trust audit (P2)” section to treat
checked-out workflow files scanned by check-soft-tier.mjs as untrusted data,
removing the claim that no decision reads their content. State only that the
scanner does not execute workflow text, fetch remote artifacts, or make network
calls, and scope the pull_request guarantee to privileged access: contents:
read, no repository secrets, and persist-credentials: false on every checkout.
- Around line 46-67: Update the proposed ci.yml shape to document six
independent matrix jobs named format-check, lint, markdown-lint, typecheck,
test, and build rather than one combined check job. Give each job explicit name
values that identify both its gate and matrix cell, and preserve the matrix
OS/node combinations and required install gating so the plan reflects all 30
emitted gate/cell jobs.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f5f3dec7-06c9-4e95-bdf7-b08da03c34f7

📥 Commits

Reviewing files that changed from the base of the PR and between 680efd1 and 76135ef.

📒 Files selected for processing (6)
  • .dev/features/ci-matrix-os-node/PLAN.md
  • .dev/floor/check-soft-tier.mjs
  • .dev/floor/check-soft-tier.test.mjs
  • .github/workflows/ci.yml
  • .github/workflows/floor.yml
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • .dev/floor/check-soft-tier.mjs

Comment on lines +74 to +76
- **Enforcement (outside the scanned workflow surface):** `floor.yml` runs `node .dev/floor/check-soft-tier.mjs .` directly (not only via its test file), and the `main protection` ruleset requires the `floor` status check. The `★ LIVE REPO-CONSISTENCY` block in `check-soft-tier.test.mjs` is retained as a third anti-vacuity residual on top of both wires.
- **Named residuals** (stated, never claimed closed):
- **R1 — other required checks are still a REPO-SETTINGS property.** This gate guarantees no workflow *asks* to be soft, and `floor` itself is now required on `main`; a *different* job left out of the required list (e.g. a matrix cell) remains invisible to this scanner. The operational note for those names still goes to the human by hand.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

gh api --paginate repos/pharn-dev/pharn-cli/rulesets \
  --jq '.[] | select(.name == "main protection") |
        {name, enforcement, required_checks: [
          .rules[]? |
          select(.type == "required_status_checks") |
          .parameters.required_status_checks[]?.context
        ]}'

Repository: pharn-dev/pharn-cli

Length of output: 228


🏁 Script executed:

#!/bin/bash
set -euo pipefail

gh api repos/pharn-dev/pharn-cli/rulesets \
  --jq '.[] | select(.name == "main protection") |
        {id, name, enforcement, bypass_actors, rules}'

Repository: pharn-dev/pharn-cli

Length of output: 255


Correct the floor enforcement claim.

The active main protection ruleset has no required status checks, so it does not require floor. Describe floor as intended enforcement and record the actual required-check configuration at HALT 2.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.dev/features/ci-matrix-os-node/PLAN.md around lines 74 - 76, Update the
enforcement statement in the plan to describe the main protection ruleset’s
floor requirement as intended rather than active enforcement. At HALT 2, record
that the active ruleset currently has no required status checks, including
floor, while preserving the direct floor invocation and residual scope.

Comment thread .github/workflows/ci.yml
Comment on lines +11 to +20
# Shared 5-cell matrix — pinned once so every gate job fans out identically.
x-ci-matrix: &ci-matrix
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, node: 20 }
- { os: ubuntu-latest, node: 22 }
- { os: ubuntu-latest, node: 24 }
- { os: windows-latest, node: 24 }
- { os: macos-latest, node: 24 }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

if command -v actionlint >/dev/null 2>&1; then
  actionlint .github/workflows/ci.yml
else
  echo "Install actionlint, then validate .github/workflows/ci.yml."
  exit 1
fi

Repository: pharn-dev/pharn-cli

Length of output: 406


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat -n .github/workflows/ci.yml
printf '\nWorkflow files:\n'
git ls-files '.github/workflows/*'
printf '\nMatrix and gate references:\n'
rg -n '^(x-ci-matrix|jobs:|  [A-Za-z0-9_-]+:|    strategy:|    needs:)|matrix\.(os|node)|coverage|markdown|build|typecheck|lint|format' .github/workflows/ci.yml

Repository: pharn-dev/pharn-cli

Length of output: 6492


🌐 Web query:

GitHub Actions workflow YAML anchors aliases supported job strategy workflow root unexpected key documentation

💡 Result:

GitHub Actions supports basic YAML anchors (&) and aliases (*) within workflow files, enabling the reuse of specific configuration blocks [1][2]. However, this support is limited and does not include the full YAML specification—specifically, YAML merge keys (<<:) are not supported [3]. Key considerations regarding this support include: - Basic Reuse: You can use anchors and aliases to deduplicate repetitive configurations such as environment variables, entire job definitions, or step sequences [2][3]. - Absence of Merge Keys: GitHub Actions does not support the YAML merge key (<<:), which is often used in other CI/CD platforms to extend or override base configurations [3]. If you attempt to use merge keys, the workflow will fail validation [3]. - Error Messages: If you encounter an unexpected key or validation error related to anchors, ensure that you are not attempting to use merge keys [3]. While some older community discussions or legacy documentation may suggest that anchors are unsupported entirely, they are now officially supported for direct aliasing [1][2]. Errors in modern workflows regarding anchors are typically due to syntax that violates the supported subset (e.g., attempting a merge) rather than the presence of anchors themselves [3]. - Recommended Alternatives: For complex configuration sharing, such as needing to extend or override base settings, GitHub recommends using official Actions features instead of relying on YAML-level primitives. These include: - Reusable workflows for sharing entire jobs or complex logic [2][4]. - Composite actions for sharing sequences of steps [4][5]. - Matrices for dynamic job generation [4]. For authoritative guidance, refer to the official documentation on reusing workflow configurations [2][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

if ! command -v actionlint >/dev/null 2>&1; then
  echo "actionlint is not available"
  exit 1
fi

echo "actionlint version:"
actionlint -version || true

python3 - <<'PY' | actionlint -
from pathlib import Path

lines = Path(".github/workflows/ci.yml").read_text().splitlines()
out = []
i = 0
while i < len(lines):
    if i == 10:  # Remove the root-level matrix comment and x-ci-matrix block.
        i = 20
        continue
    if i == 22:  # Add the anchor at format-check.strategy.
        out.extend([
            "    strategy: &ci-matrix",
            "      fail-fast: false",
            "      matrix:",
            "        include:",
            "          - { os: ubuntu-latest, node: 20 }",
            "          - { os: ubuntu-latest, node: 22 }",
            "          - { os: ubuntu-latest, node: 24 }",
            "          - { os: windows-latest, node: 24 }",
            "          - { os: macos-latest, node: 24 }",
        ])
    if i == 27:  # Remove format-check's old alias line.
        i += 1
        continue
    out.append(lines[i])
    i += 1

print("\n".join(out))
PY

Repository: pharn-dev/pharn-cli

Length of output: 413


🏁 Script executed:

#!/bin/bash
set -euo pipefail

if ! command -v actionlint >/dev/null 2>&1; then
  echo "actionlint is not available"
  exit 1
fi

python3 - <<'PY' >/tmp/ci-corrected.yml
from pathlib import Path

src = Path(".github/workflows/ci.yml").read_text().splitlines()
out = []
i = 0
while i < len(src):
    # Remove lines 11-20 from the current file.
    if 10 <= i <= 19:
        i += 1
        continue
    # Insert the anchor before format-check.name.
    if i == 23:
        out.extend([
            "    strategy: &ci-matrix",
            "      fail-fast: false",
            "      matrix:",
            "        include:",
            "          - { os: ubuntu-latest, node: 20 }",
            "          - { os: ubuntu-latest, node: 22 }",
            "          - { os: ubuntu-latest, node: 24 }",
            "          - { os: windows-latest, node: 24 }",
            "          - { os: macos-latest, node: 24 }",
        ])
    # Remove format-check's existing strategy alias.
    if i == 27:
        i += 1
        continue
    out.append(src[i])
    i += 1

print("\n".join(out))
PY

echo "Corrected workflow excerpt:"
nl -ba /tmp/ci-corrected.yml | sed -n '8,38p'
echo
actionlint /tmp/ci-corrected.yml

Repository: pharn-dev/pharn-cli

Length of output: 226


🏁 Script executed:

#!/bin/bash
set -euo pipefail

if ! command -v actionlint >/dev/null 2>&1; then
  echo "actionlint is not available"
  exit 1
fi

python3 - <<'PY' >/tmp/ci-corrected.yml
from pathlib import Path

text = Path(".github/workflows/ci.yml").read_text()
old_block = """# Shared 5-cell matrix — pinned once so every gate job fans out identically.
x-ci-matrix: &ci-matrix
  fail-fast: false
  matrix:
    include:
      - { os: ubuntu-latest, node: 20 }
      - { os: ubuntu-latest, node: 22 }
      - { os: ubuntu-latest, node: 24 }
      - { os: windows-latest, node: 24 }
      - { os: macos-latest, node: 24 }

"""
assert old_block in text
text = text.replace(old_block, "", 1)

needle = """  format-check:
    # Explicit name pins check identity to gate + cell so branch protection can
"""
replacement = """  format-check:
    strategy: &ci-matrix
      fail-fast: false
      matrix:
        include:
          - { os: ubuntu-latest, node: 20 }
          - { os: ubuntu-latest, node: 22 }
          - { os: ubuntu-latest, node: 24 }
          - { os: windows-latest, node: 24 }
          - { os: macos-latest, node: 24 }
    # Explicit name pins check identity to gate + cell so branch protection can
"""
assert needle in text
text = text.replace(needle, replacement, 1)

old_strategy = "    strategy: *ci-matrix\n"
assert text.count(old_strategy) == 6
text = text.replace(old_strategy, "", 1)  # The first occurrence is format-check.
print(text, end="")
PY

echo "Corrected workflow excerpt:"
sed -n '1,45p' /tmp/ci-corrected.yml
echo
actionlint /tmp/ci-corrected.yml

Repository: pharn-dev/pharn-cli

Length of output: 1490


Move the matrix anchor to a supported workflow node.

GitHub Actions rejects x-ci-matrix at the workflow root. Move the matrix to format-check.strategy, replace its existing alias, and keep strategy: *ci-matrix in the other five jobs.

🧰 Tools
🪛 actionlint (1.7.12)

[error] 12-12: unexpected key "x-ci-matrix" for "workflow" section. expected one of "concurrency", "defaults", "env", "jobs", "name", "on", "permissions", "run-name"

(syntax-check)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 11 - 20, Move the x-ci-matrix anchor
from the workflow root into format-check.strategy, replacing that job’s existing
matrix alias while preserving the fail-fast and include entries. Keep strategy:
*ci-matrix unchanged in the other five jobs.

Source: Linters/SAST tools

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