GitHub Action reference
The packaged Action runs the same npm package the CLI does. It sets up Node,
fetches manni, and shells out to one manni command. Nothing it does is
unavailable from the CLI. It exists so the invocation lives in one place,
instead of being copy-pasted into every repository that wants it.
The Action runs manni check unless told otherwise.
That is every check the repository has set up, as one gate.
- uses: actions/checkout@v7 with: fetch-depth: 0- uses: hawkeyexl/manni@v4Inputs
Section titled “Inputs”Every input is optional. An input left unset contributes nothing to the command line, and it does not become an empty flag.
| Input | Default | Maps to |
|---|---|---|
command | check | The words after manni, such as meta validate or cite check. Split on whitespace and never run through a shell. Empty means check. |
paths | none | Positional paths after the command. Files, directories, or globs. Space-separated, or one per line. Use lines for any path containing a space. Unset, the command reads the collections in your config. |
schema | none | One -s per line, for a command that takes -s, such as meta validate. |
config | none | -c |
format | github | --format. An empty string passes no --format, for a command that has none. |
output-file | none | Sends the command’s stdout to this file, relative to the workspace. Its directory is created when missing. stderr stays in the job log. |
version | latest | The npm version range fetched, as @hawkeyexl/manni@<version>. Also accepts a path or tarball. check needs 4.4.0 or newer. |
args | none | Appended verbatim after everything else, for anything the inputs above do not cover. --collection reaches the Action this way. |
node-version | 24 | Passed to actions/setup-node. manni needs 24 or newer. |
The Action runs the command words, then the paths, each -s, -c, --format
and args, in that order. It logs that line before the run, with > <file>
when output-file is set:
manni meta validate docs/ guides/my\ notes/ -s ./schemas/page.schema.json --format sarif > reports/manni.sarifmanni reads its stdin from /dev/null. manni check reads stdin for a Claude
Code hook, so the redirect keeps a CI run from waiting on the runner’s pipe.
Globs reach manni unexpanded
Section titled “Globs reach manni unexpanded”A paths value like docs/**/*.md is passed through as that string. The Action
disables shell globbing deliberately. manni applies its own extension
filtering and gitignore rules.
Letting the runner expand the pattern first would substitute the shell’s view of
the tree for manni’s. It would do so silently, and differently depending on
the working directory.
Selecting a collection
Section titled “Selecting a collection”There is no collection input. --collection reaches the Action through args,
the documented escape hatch for every flag the inputs above do not name. It is a
meta validate flag, so the step names that command:
- uses: hawkeyexl/manni@v4 with: command: meta validate args: --collection guidesThe flag repeats rather than splitting on commas, so two collections are two occurrences on the same line:
- uses: hawkeyexl/manni@v4 with: command: meta validate args: --collection guides --collection blogLeave paths unset when you do this. --collection selects from the config, so
it cannot be combined with positional paths. A workflow that sets both fails the
step with exit 2. The message is
--collection selects a configured collection; it cannot be combined with paths.
With neither, the run covers every declared collection, which is what the
paths-less form above already did.
Run every check
Section titled “Run every check”With no inputs, the Action runs manni check --format github. Every check the
repository has set up reports its findings as annotations on the pull request.
A clean run exits 0.
- uses: hawkeyexl/manni@v4manni check --format githubA finding at error level fails the step with exit 1. Here docs/limits.md
lacks a description, and the annotation lands on the page:
manni check --format github::error file=docs/limits.md,line=1::[manni:core:1.0.0] (root) must have required property 'description'The checkout needs fetch-depth: 0 when the repository has citations.
manni cite check reads the commit each pin was minted at, and a shallow clone
cannot supply it. Gate every check a repo has set up
says what puts a domain in the gate.
Run one command
Section titled “Run one command”command picks any other manni command. This runs the citation check alone:
- uses: hawkeyexl/manni@v4 with: command: cite check paths: docs/manni cite check docs/ --format githubcommand: meta validate runs the metadata check alone, as the Action did
before it ran check. These inputs run the same command line as then:
- uses: hawkeyexl/manni@v4 with: command: meta validate paths: "docs/**/*.md" schema: ./schemas/page.schema.jsonmanni meta validate docs/\*\*/\*.md -s ./schemas/page.schema.json --format githubA command with no --format option needs format: "", or it fails on the
default github:
- uses: hawkeyexl/manni@v4 with: command: site build format: ""manni site buildcommand: status runs manni status,
which takes pretty or json. So set format to one of those, or to "".
It gates nothing, and exits 0 whatever the report says.
When the command is wrong
Section titled “When the command is wrong”The Action does not judge command. manni does, with its own message and exit
2, so the step fails and exit-code is 2.
| Workflow | Printed | Exit |
|---|---|---|
command: lint chek | error: unknown command 'chek' then (Did you mean check?) | 2 |
command: validate | error: unknown command 'validate' then (`validate` is a metadata command: try `manni meta validate`) | 2 |
command: check with schema | error: unknown option '-s' | 2 |
command: site build, format left at github | error: unknown option '--format' | 2 |
command: check, format: junit | error: option '-f, --format <format>' argument 'junit' is invalid. Allowed choices are pretty, json, github, sarif. | 2 |
command: check, no config | manni: No manni.config.yaml found from <workspace> up to the repository root. Create one there with a collections: entry for your docs. | 2 |
command: check, nothing set up | manni: Nothing is set up to check in manni.config.yaml. Run manni status to see what each domain needs. | 2 |
version: "4.3", command unset | error: unknown command 'check' | 2 |
Upload SARIF to code scanning
Section titled “Upload SARIF to code scanning”format: sarif with output-file writes one SARIF log that
github/codeql-action/upload-sarif can read. Without output-file the log
lands in the job log, where the upload cannot reach it.
jobs: manni: runs-on: ubuntu-latest permissions: contents: read security-events: write # upload-sarif needs it; without it the upload is a 403 steps: - uses: actions/checkout@v7 with: fetch-depth: 0 - id: manni uses: hawkeyexl/manni@v4 with: format: sarif output-file: manni.sarif - if: ${{ !cancelled() && hashFiles('manni.sarif') != '' }} uses: github/codeql-action/upload-sarif@v4 with: sarif_file: manni.sarifThe manni step still fails the job on exit 1 or 2. The upload runs anyway,
because !cancelled() replaces the default success() condition.
Leave category unset. manni check -f sarif gives each domain its own
manni/<domain>/ category, and a last manni/check/ run names what was
skipped.
When manni fails before printing anything, the Action removes the empty file,
and hashFiles skips the upload. upload-sarif rejects a zero-byte log.
The github format is workflow commands, which annotate only from the job log.
So output-file with format: github prints a warning and runs on:
::warning::output-file sends the github format's workflow commands to <path>, so no annotations appear. Pick another format to write a file.Outputs
Section titled “Outputs”| Output | Value |
|---|---|
exit-code | manni’s exit code. 0 clean, 1 an error-level finding, 2 the command could not run or was not a valid manni command. |
exit-code is only observable with continue-on-error: true. Without it a
non-zero code has already failed the step, so there is nothing left to read:
- id: manni uses: hawkeyexl/manni@v4 continue-on-error: true with: format: json output-file: manni.json- run: jq -r '.checks[] | "\(.status) \(.command)"' manni.json- if: steps.manni.outputs.exit-code == '1' run: echo "Findings, but not blocking this job."Versioning
Section titled “Versioning”@vN is a moving tag. On each stable release, the release job moves it to the
newest release of that major.
Pin a full tag such as uses: hawkeyexl/manni@v4.27.2 instead when you want an
immutable Action. That is the usual trade between getting fixes automatically
and knowing exactly what runs.
The Action tag chooses the Action’s code. The version input chooses the CLI
it runs, and defaults to latest, the newest published CLI. Set version to a
major such as 2, or to an exact version, when you want a reproducible CLI.
The equivalent by hand
Section titled “The equivalent by hand”The Action replaces this, which remains supported. For the metadata check alone,
examples/manni.yml
shows the same steps with meta validate:
- uses: actions/checkout@v7 with: fetch-depth: 0- uses: actions/setup-node@v7 with: node-version: 24- run: npx -y @hawkeyexl/manni check --format github < /dev/nullUse the hand-written form on a runner that cannot use a composite action, or
when you need to control the Node setup yourself. Everything else moves inside
the Action, including the CLI version it fetches, the --format github default,
and the Node version. So changing the recommended invocation is a tag move, rather than an
edit in every repository.