Run the Markdown Linter on markdownlint-cli2 instead of Super Linter - #10640
Conversation
The Markdown Linter workflow has never run. All 63 runs since 2026-05-22 ended in
startup_failure, because it is the only workflow in the repository that references an
action outside actions/* and github/*, and this repository restricts Actions to a
selected list:
patterns_allowed: dotnet/arcade/.github/workflows/*,
peter-evans/create-pull-request@*,
DavidAnson/markdownlint-cli2-action@*
verified_allowed: false
super-linter/super-linter is not on that list, so the run was rejected before any job
started. This is the same failure that took out the markdownlint gate in #10636.
Swap Super Linter for markdownlint-cli2, pinned to 0.23.1, the version bundled by
DavidAnson/markdownlint-cli2-action@v24.1.0 that markdownlint.yml already runs on every
pull request. The scheduled report and the pull request gate now apply the same rules
from .markdownlint-cli2.jsonc, including its ignores, so the report cannot flag anything
the gate would let through.
Two more bugs fixed along the way:
- The lint step no longer fails the job when it finds violations. Super Linter failed the
job, which skipped the agent that was supposed to write the report, so the workflow
could not have reported violations even if it had started.
- Super Linter ran with VALIDATE_ALL_CODEBASE=false, which lints only files that differ
from the default branch. On a scheduled run on main that is nothing. markdownlint-cli2
scans all 86 tracked Markdown files.
Also drop the now-unused packages: read and statuses: write permissions, and the dead
check-results step whose output no job consumed.
The workflow keeps no source: field. It came from
githubnext/agentics/workflows/markdown-linter.md@main, and relinking it would let
gh aw update restore the blocked action and break the workflow again.
Compiled with gh aw v0.86.0 using --action-mode release --action-tag 435186c5, matching
the pins in the other lock files; .github/scripts/check_action_pins.py passes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Replaces the blocked Super Linter job with markdownlint-cli2 for scheduled Markdown reporting.
Changes:
- Aligns scheduled linting with the pull-request Markdown gate.
- Preserves lint logs while reducing permissions.
- Regenerates the compiled agentic workflow.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
.github/workflows/markdown-linter.md |
Defines the new markdownlint-cli2 workflow. |
.github/workflows/markdown-linter.lock.yml |
Compiles the updated workflow definition. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The step swallowed every exit code, so an npm or configuration failure looked the same as a clean run: the log held a stack trace instead of lint results, and the agent went on to report "no issues" over a run that never linted. markdownlint-cli2 exits 0 clean, 1 with violations, and 2 when it fails to run. Keep 0 and 1 green, because violations are what the agent reports on, and fail the job for anything above that. Also update the workflow catalog entry, which still described this workflow as running Super Linter. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Conflict in .github/workflows/markdown-linter.lock.yml: #10657 bumped the gh-aw setup pin from 435186c5 to 48e5fa3f across every lock file, while this branch removed super-linter from the same header block. Resolved by regenerating the lock file from its .md source with `gh aw compile --action-mode release --action-tag 48e5fa3f` rather than editing the generated file, so it picks up the new pin and keeps this branch's changes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
npx exits 1 when it cannot resolve or download the package, which is the same code markdownlint-cli2 uses for "violations found". A registry outage therefore kept the job green and handed the agent an npm error log to report as if it were lint results — the misleading report this step was meant to prevent. Install the pinned version in a separate step, which fails the job on its own, and run the installed binary so the tolerated exit code 1 can only come from markdownlint-cli2 itself. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the scheduled workflow aligned with markdownlint-cli2-action v24.2.0 on main. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 021b7d71-92b8-40a1-a0ab-5a6d768c5b1a
Conflict in .github/workflows/markdown-linter.lock.yml: #10678 upgraded the agentic workflows to gh-aw 0.86.2, which changes the compiler version and moves the setup action to github/gh-aw-actions, while this branch rewrote the same job. Resolved by upgrading the local gh aw to v0.86.2 and regenerating the lock file from its .md source with `--action-mode action --action-tag v0.86.2`, matching how the other lock files on main are now built, rather than editing the generated file. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The agent job needs the markdownlint job, so failing that job on a tooling error skipped the agent and, with it, detection and safe_outputs. A registry outage or a broken config produced a red scheduled run and no issue at all, which is the unattended silence this workflow is meant to remove. Keep the job green and hand the agent a log whose first line is MARKDOWNLINT_RUN_FAILED, and teach the prompt to check for that marker before anything else and report a workflow failure rather than lint findings. The npm install keeps its own step and marks the log the same way instead of failing, so an install failure is reported rather than swallowed. Also stop Dependabot from bumping DavidAnson/markdownlint-cli2-action on its own. It pins the CLI version it bundles, and this workflow installs that same version by hand, so an unattended bump of one side would silently give the scheduled report and the pull request gate different rule sets. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Conflict in .github/dependabot.yml: main added an ignore entry for github/gh-aw/actions/setup while this branch added one for DavidAnson/markdownlint-cli2-action. Both are wanted, so both are kept. The lock file merged cleanly and was left as merged rather than regenerated: the local gh aw resolves gh-aw's own actions/checkout scaffolding to v7.0.0, while .github/aw/actions-lock.json pins v7.0.1, so recompiling here reintroduces the stale pins of #10258. The merged file keeps main's correct pins and this branch's lint job, and the pin audit passes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Conflict in .github/workflows/markdown-linter.lock.yml: #10729 pinned the threat-detection model on every agentic workflow, so both sides carry a different generated metadata hash for the same file. The .md source merged cleanly and keeps both changes, so the lock file was regenerated from it with `gh aw compile --action-mode action --action-tag v0.86.2`. The result has main's threat-detection pin and this branch's lint job, and the pin audit passes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Without timeout-minutes the job inherits GitHub's six-hour default, so a stalled npm install or linter holds the workflow's concurrency group for hours and then fails the job. The agent job needs this one, so that failure also skips the reporting chain and the scheduled run says nothing -- the silence this workflow exists to remove. The pull request gate finishes in seconds under a 5 minute cap; 10 leaves headroom. Also correct the workflow catalog: this workflow has only schedule and workflow_dispatch triggers, so the trigger column is "Schedule + manual". Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
.github/workflows/markdown-linter.md:88
- A hung linter is also killed only by the job timeout, bypassing the
status > 1marker path and skipping the reporting chain. Give the process its own timeout below the job cap so the resulting status is recorded asMARKDOWNLINT_RUN_FAILEDand uploaded for the agent.
markdownlint-cli2 "**/*.md" > markdownlint.log 2>&1 || status=$?
The job-level timeout added in the previous commit caps the runaway, but GitHub kills the job when it fires, so the script never runs its marker branch: no log is written, no artifact is uploaded, and the agent job is skipped. A stall was still a red run with no issue. Wrap both commands in `timeout 5m`, below the job's 10 minute cap. timeout exits 124, which is greater than 1 and so already routes into the existing failure branch; the messages name the timeout specifically. Verified against a real stall: exit 124 marks the log and the step still exits 0. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
.github/workflows/markdown-linter.md:69
- GNU
timeoutonly sends SIGTERM by default; if npm or one of its child processes does not terminate, this command keeps waiting until the job-level timeout kills the job, so no marker is uploaded. Add a short kill-after grace period so a stalled install is guaranteed to reach the failure branch.
timeout 5m npm install --global markdownlint-cli2@0.23.2 > install.log 2>&1 || status=$?
.github/workflows/markdown-linter.md:101
- As with the install timeout, this only sends SIGTERM. If the linter does not terminate, the job-level timeout fires before this branch can prepend
MARKDOWNLINT_RUN_FAILEDor upload the log. Use a kill-after grace period to guarantee termination and preserve the reporting path.
timeout 5m markdownlint-cli2 "**/*.md" > markdownlint.log 2>&1 || status=$?
The two commands run in sequence, so a 5 minute cap on each could consume the whole 10 minute job cap before the second one wrote and uploaded its failure marker. GitHub would then kill the job and the stall would go unreported again, which is what the command caps were added to prevent. Cut both command caps to 3 minutes: 3 + 3 leaves 4 minutes for checkout, the marker write and the artifact upload. The job takes about 15 seconds in practice, so 3 minutes is still roughly 45x the observed install and 18x the lint. The comments now state the arithmetic so the three numbers stay consistent. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
`timeout` alone sends only SIGTERM, so a command that ignores it keeps running and the job-level timeout kills the job before the failure marker is written or uploaded. The command caps could not bound a genuinely hung process. Add --kill-after=30s to both commands. The grace period forces SIGKILL, which surfaces as 137 rather than 124, so both codes now count as a timeout. Worst case is 3m plus 30s per command, 7 minutes for the pair, still inside the 10 minute job cap. Verified against a process that traps and ignores SIGTERM: without the grace period it outlived its cap entirely; with it the command is killed, reports 137 and the log is marked. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Amaury Levé (Evangelink)
left a comment
There was a problem hiding this comment.
Reviewed the latest head. The previously identified version-coupling, failure-reporting, timeout, and documentation issues are resolved, and the generated workflow, action pins, permissions, artifact flow, and shell failure paths are consistent.
The Markdown Linter workflow has never run. All 63 runs since 2026-05-22 ended in
startup_failure, because it is the only workflow in the repository referencing an action outsideactions/*andgithub/*, and this repository restricts Actions to a selected list that does not includesuper-linter/super-linter. The run is rejected before any job starts, which is why there is no log, no check run and no annotation to notice.Run
markdownlint-cli2instead, pinned to 0.23.2 — the version bundled byDavidAnson/markdownlint-cli2-action@v24.2.0thatmarkdownlint.ymlruns on every pull request. The scheduled report and the gate now apply the same rules from.markdownlint-cli2.jsonc, including its ignores, so the report cannot flag anything the gate would let through..github/dependabot.ymlignores that action so the two pins can only move together, by hand; Dependabot only sees the action and would otherwise bump one side alone and silently give the two different rule sets.Two further bugs: the lint step failed the job on violations, which skipped the agent that writes the report, so the workflow could only ever have reported a clean tree; and
VALIDATE_ALL_CODEBASE=falselints only files differing from the default branch, which on a scheduled run onmainis nothing. It now scans all 86 tracked Markdown files.Failure handling
The
agentjob needsmarkdownlint, anddetectionandsafe_outputschain offagent, so a red lint job takes the whole reporting path down with it and the scheduled run says nothing at all. The lint job therefore always succeeds, including when the linter could not run. A red run is not the failure signal here; the issue the agent files is.The two outcomes are kept apart in the log rather than in the exit code:
markdownlint-cli2exited 0 or 1. The log holds findings and the agent reports on them as usual.markdownlint-cli2exited above 1: 2 is an execution or configuration error, 124 or 137 a timeout. The log is rewritten withMARKDOWNLINT_RUN_FAILEDas its first line, followed by the error output. The prompt checks that marker before anything else and must then file a workflow-failure issue saying Markdown went unlinted, rather than analysing the error text as findings or reporting a clean run.A
::error::annotation is still emitted in the failure case, so the run surfaces in the UI without breaking the reporting chain.Stalls are bounded at three levels, because each one alone leaves a gap. Each command runs under
timeout --kill-after=30s 3m: the plain job timeout would kill the step before it could mark the log, andtimeoutwithout a kill grace only sends SIGTERM, which a hung process can ignore.timeout-minutes: 10on the job is the backstop. The numbers have to keep that order — worst case is 3m30s per command, so 7 minutes for the pair, leaving 3 minutes for checkout, the marker write and the artifact upload.Also drops the unused
packages: readandstatuses: writepermissions and a dead step whose output no job consumed. The workflow keeps nosource:field, sogh aw updatecannot restore the blocked action; a comment in the frontmatter records why.Security review
Required by the gh-aw safe-update warning on the first compile:
super-linter/super-linter@4ce20838is removed and no action is added —actions/checkoutandactions/upload-artifactonly move within the file at their existing pins. No secrets added or removed; the job no longer needs theGITHUB_TOKENSuper Linter consumed. Permissions are reduced, never widened. The one new network access isnpm install --global markdownlint-cli2@0.23.2fetching that pinned version from the npm registry.Verification
bash -ewith the linter stubbed: 0 and 1 leave the log untouched; 2, 124 and 137 prependMARKDOWNLINT_RUN_FAILED; the step exits 0 in every case so the reporting chain survives.npx markdownlint-cli2@0.23.2 "**/*.md": 86 files, 0 issues.python .github/scripts/check_action_pins.py: passes, 2343 references across 49 files.activation,markdownlintandagentall green, install and lint steps succeeding, no marker in the log. The job takes about 20 seconds against the 10 minute cap.Note for whoever recompiles this: the lock file is generated with
gh aw compile --action-mode action --action-tag v0.86.2. A locally installedgh awresolves gh-aw's ownactions/checkoutscaffolding to v7.0.0 while.github/aw/actions-lock.jsonpins v7.0.1, so a local recompile on the wrong version reintroduces the stale pins of #10258 and fails the pin audit.Fixes the dead scheduled Markdown report. History for the related gate: #10163, #10196, #10214.
🤖