Skip to content

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@v4

Every input is optional. An input left unset contributes nothing to the command line, and it does not become an empty flag.

InputDefaultMaps to
commandcheckThe words after manni, such as meta validate or cite check. Split on whitespace and never run through a shell. Empty means check.
pathsnonePositional 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.
schemanoneOne -s per line, for a command that takes -s, such as meta validate.
confignone-c
formatgithub--format. An empty string passes no --format, for a command that has none.
output-filenoneSends the command’s stdout to this file, relative to the workspace. Its directory is created when missing. stderr stays in the job log.
versionlatestThe npm version range fetched, as @hawkeyexl/manni@<version>. Also accepts a path or tarball. check needs 4.4.0 or newer.
argsnoneAppended verbatim after everything else, for anything the inputs above do not cover. --collection reaches the Action this way.
node-version24Passed 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.sarif

manni 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.

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.

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 guides

The 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 blog

Leave 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.

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@v4
manni check --format github

A 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.

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 github

command: 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.json
manni meta validate docs/\*\*/\*.md -s ./schemas/page.schema.json --format github

A 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 build

command: 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.

The Action does not judge command. manni does, with its own message and exit 2, so the step fails and exit-code is 2.

WorkflowPrintedExit
command: lint chekerror: unknown command 'chek' then (Did you mean check?)2
command: validateerror: unknown command 'validate' then (`validate` is a metadata command: try `manni meta validate`)2
command: check with schemaerror: unknown option '-s'2
command: site build, format left at githuberror: unknown option '--format'2
command: check, format: juniterror: option '-f, --format <format>' argument 'junit' is invalid. Allowed choices are pretty, json, github, sarif.2
command: check, no configmanni: 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 upmanni: Nothing is set up to check in manni.config.yaml. Run manni status to see what each domain needs.2
version: "4.3", command unseterror: unknown command 'check'2

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.

.github/workflows/docs.yml
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.sarif

The 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.
OutputValue
exit-codemanni’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."

@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 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/null

Use 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.