| name | qtpass-linting |
| description | QtPass CI/CD workflow - run GitHub Actions locally with act, linters, formatters |
| license | GPL-3.0-or-later |
| metadata | {"audience":"developers","workflow":"linting"} |
QtPass Linting and CI Workflow
Super-linter v8 specifics (CI pin: v8.6.0)
The lint workflow uses super-linter/super-linter@v8.6.0 which resolves to image ghcr.io/super-linter/super-linter:v8.6.0. Critical quirks learned the hard way:
Config file locations
LINTER_RULES_PATH defaults to .github/linters/ — every per-linter config file that super-linter explicitly passes via --config <path> lives there, not at the repository root.
| Linter | Config file |
|---|
| zizmor | .github/linters/zizmor.yaml |
Other linters auto-discover their own dotfiles like .codespellrc, .clang-format, .commitlintrc.js, .jshintrc, .jshintignore, .yaml-lint.yml, .codecov.yml from the repository root via the linter's own conventions — those don't go under .github/linters/.
super-linter.env rules
- Pure
KEY=value only — no comments, no empty lines, alphabetical (dotenv-linter UnorderedKey rule). The workflow does cat .github/super-linter.env >> $GITHUB_ENV and $GITHUB_ENV rejects everything else.
- Document the rationale for non-obvious vars in the workflow YAML (above the
cat step), not in the env file.
GITHUB_* env vars set via $GITHUB_ENV don't propagate to docker-based actions like super-linter. So you cannot override e.g. GITHUB_ACTIONS_ZIZMOR_CONFIG_FILE this way — put the config at the default-discovered location instead.
Reproducing CI locally
act is unreliable for super-linter — the floating :v7/:v8 tags drift, and act's mock GitHub event JSON lacks fields that v8 super-linter needs (forced for push events). The reliable reproduction is the exact CI image via Docker:
docker run --rm -v "$PWD:/work" -w /work \
--entrypoint zizmor ghcr.io/super-linter/super-linter:v8.6.0 \
--config .github/linters/zizmor.yaml .github/workflows/
docker run --rm -v "$PWD:/work" -w /work \
--entrypoint clang-format ghcr.io/super-linter/super-linter:v8.6.0 \
--style=file --dry-run --Werror path/to/file.cpp
Or install native binaries: cargo install zizmor, distro packages for clang-format/yamllint/codespell.
Tooling-version highlights
- clang-format 20 — modern; matches recent distros. (Was 17.0.6 in v7.1.0 — a notable mismatch source pre-v8 bump.)
- zizmor 1.23.1 — default policy is
hash-pin; we override to ref-pin in the config (version tags + dependabot cooldown).
- commitlint 20 — picks up
.commitlintrc.js from the repository root.
- JSHint — only runs via Hound (not super-linter v8). Default ES5; we set
esversion: 11 in .jshintrc.
The Act Pattern (caveat: super-linter is tricky)
act works well for most workflows but the super-linter image has version drift and event-mock issues — see "Reproducing CI locally" above for the reliable alternative.
Always run local CI before pushing PRs. Use act for most workflows; for super-linter workflows, prefer the Docker-based alternative described above (version drift and event-mock issues make act unreliable there).
Why Use act?
- Catch linter failures before pushing
- Validate changes without waiting for CI
- Faster iteration loop
The Workflow
git add .
act push -W .github/workflows/linter.yml -j build
Quick Reference
| Task | Command |
|---|
| Run linter | act push -W .github/workflows/linter.yml |
| Run linter (specific job) | act push -W .github/workflows/linter.yml -j build |
| Run build & tests | act push -W .github/workflows/ccpp.yml |
| Run docs | act push -W .github/workflows/docs.yml |
| Run reuse check | act push -W .github/workflows/reuse.yml |
Available Workflows
Linter Workflow (.github/workflows/linter.yml)
Runs super-linter with many linters:
- GITLEAKS - Secret detection
- CHECKOV - Infrastructure scanning
- CLANG_FORMAT - C++ formatting
- ACTIONLINT - GitHub Actions YAML
- PRETTIER - Web/config file formatting
- Markdown - Markdown linting
- NATURAL_LANGUAGE - Natural language checks
- YAML - YAML linting
act push -W .github/workflows/linter.yml -j build
Build & Test Workflow (.github/workflows/ccpp.yml)
QtPass build with Qt5/Qt6 matrix, runs unit tests, generates coverage:
act push -W .github/workflows/ccpp.yml
Tests against:
- Ubuntu + Qt 6.8
- macOS + Qt 6.8
- Windows + Qt 6.8
- Ubuntu + Qt 5.15
Note: Qt installation may fail in act due to environment limitations. Real CI handles this.
Documentation Workflow (.github/workflows/docs.yml)
act push -W .github/workflows/docs.yml
Reuse Compliance (.github/workflows/reuse.yml)
Check license headers and REUSE compliance:
act push -W .github/workflows/reuse.yml
Doxygen Documentation Linting
The CI enforces zero Doxygen warnings via docs.yml. WARN_AS_ERROR = FAIL_ON_WARNINGS in Doxyfile causes the step to fail on any undocumented public symbol.
CI pins Doxygen 1.17.0 (local Doxygen may be older and miss/add warnings). The docs.yml install step downloads that pinned binary from the GitHub release mirror first, falling back to doxygen.nl, with retries — doxygen.nl has intermittent outages that previously caused spurious docs failures (a red docs check is often a download blip, not your change; re-run before debugging).
Run Doxygen Locally
doxygen Doxyfile
Enforced Doxyfile Settings
| Setting | Value | Purpose |
|---|
FILE_PATTERNS | *.cpp *.h *.md | Includes cpp, header, and Markdown files |
EXTRACT_ALL | NO | Required for WARN_NO_PARAMDOC to work |
WARN_NO_PARAMDOC | YES | Requires @param/@return on all public symbols |
WARN_AS_ERROR | FAIL_ON_WARNINGS | Fails CI on any warning |
QUIET | NO | Progress output shown (not an error) |
Doxygen Comment Style
Use /** */ blocks with @brief, @param, @return:
Common Warning Causes
- Unnamed parameters in declarations:
void foo(int) — name all parameters: void foo(int count)
- Orphaned doc blocks: A
/** ... */ not immediately preceding its declaration is misattributed. Move the block directly above the declaration.
- Missing
@return: Enforced with current settings (WARN_NO_PARAMDOC = YES) — include @return for non-void functions
- Signals with unnamed params: Qt signals also need named parameters and
@param docs
@xyz typos: Doxygen treats unknown @word as commands — use @brief Like not @like
Coverage Report (optional)
Create a temporary Doxyfile override to enable XML for coverxygen (base Doxyfile may not have XML enabled):
cp Doxyfile Doxyfile.xml
echo "GENERATE_XML = YES" >> Doxyfile.xml
echo "XML_OUTPUT = xml" >> Doxyfile.xml
doxygen Doxyfile.xml
pip install coverxygen
python -m coverxygen --xml-dir xml/ --src-dir . --output coverage.info
Common Linters
Gitleaks (Secret Detection)
Detects API keys, tokens, passwords in code.
gitleaks detect
Common Fixes:
- Don't use test values that look like API keys (e.g., "ABC123DEF456", "sk-xxx")
- Use generic test strings: "testkey123", "/usr/bin/pass", "example.com"
Clang Format (C++)
clang-format --style=file --dry-run src/main.cpp
clang-format --style=file -i src/main.cpp
Shfmt (Shell Scripts)
Formats shell scripts in scripts/ folder. Uses LLVM style (matches clang-format).
Installation:
brew install shfmt
go install mvdan.cc/sh/v3/cmd/shfmt@latest
shfmt -d scripts/*.sh
shfmt -w scripts/*.sh
Clangd (LSP Analysis)
Clangd provides deep static analysis via LSP. Requires compile_commands.json:
./scripts/generate-compile-commands.sh
clangd --check=src/gpgkeystate.cpp
Common diagnostics:
[performance-unnecessary-copy-initialization] - Use const T& instead of const T
[readability-static-definition] - Consider making static definitions inline
Using "(fix available)" in editors:
| Editor | Command |
|---|
| Visual Studio Code | Click 💡 or Ctrl+. |
| JetBrains | Alt+Enter |
| Neovim | :lua vim.lsp.buf.code_action() |
Prettier (Web/Config)
npx prettier --write README.md
npx prettier --write .github/workflows/*.yml
npx prettier --write FAQ.md
npx prettier --write ".opencode/skills/*/SKILL.md"
textlint (Natural Language)
Super-linter's NATURAL_LANGUAGE check runs textlint with textlint-rule-terminology
(canonical capitalisation: Git, Ubuntu, SFTP, NixOS, Bash, …). The
repository has no textlint config, so plain npx textlint errors with "No
rules found" — load the rule explicitly via -p:
npx -p textlint -p textlint-rule-terminology textlint --rule terminology AGENTS.md
npx -p textlint -p textlint-rule-terminology textlint --rule terminology \
*.md scripts/*.md .github/**/*.md .opencode/skills/*/SKILL.md
npx -p textlint -p textlint-rule-terminology textlint --rule terminology --fix \
*.md scripts/*.md .github/**/*.md .opencode/skills/*/SKILL.md
Important: Super-linter only lints the files changed in the PR, so
terminology errors in untouched Markdown files sit latent and only blow up when
someone next edits that file. When fixing one terminology error, sweep the
whole tree with the second command above and clean up any other hits in the
same PR.
Prettier Patterns
Prettier auto-fixes many linting issues. Run before act:
npx prettier --write "**/*.md" "**/*.yml" "**/*.json" "**/*.html" "**/*.css"
Markdown Natural Language Issues
If NATURAL_LANGUAGE fails:
- Use single-word forms instead of two-word combinations
- Use full terms instead of abbreviations
- Use proper spacing and punctuation
npx prettier --write README.md
act push -W .github/workflows/linter.yml -j build
Troubleshooting
Qt Installation Fails in act
The install-qt-action may fail in local act due to missing downloads. This is expected - real CI works fine.
Linter Fails Due to Missing Files
Some checks need files generated during CI. Run full build first:
qmake6 -r CONFIG+=coverage
make -j4
Gitleaks False Positives
If gitleaks flags test data:
- Use generic test values (not like "ABC123DEF456")
- Add to
.gitleaksignore if truly non-sensitive
act Unknown Flag Error
Make sure act is installed and up to date:
act --version
brew upgrade act
CI Environment Variables
Some linters need secrets or tokens. In local act, these may not be available:
act push -W .github/workflows/ccpp.yml --secret-map "CODECOV_TOKEN=fake"
GitHub Actions Files
| File | Purpose |
|---|
.github/workflows/linter.yml | Super-linter (many checks) |
.github/workflows/ccpp.yml | Build & test with Qt |
.github/workflows/docs.yml | Doxygen docs generation |
.github/workflows/reuse.yml | REUSE compliance |
.github/super-linter.env | Linter configuration |
Run Before PR Checklist
THIS IS THE PATTERN - always run before pushing:
npx prettier --write "**/*.md" "**/*.yml"
npx prettier --check "**/*.md"
act push -W .github/workflows/linter.yml -j build
git fetch upstream
git pull upstream main --rebase
git push
Note: Prettier catches most issues. act is recommended but may fail on new branches (see below).
Note on act
act may fail on new branches with error: fatal: ambiguous argument 'HEAD~0'
This is a known issue with the tool, not your code. When this happens:
- Skip the act step
- The
prettier --check step is sufficient for most cases
- Trust that formatting is correct
- The real GitHub CI will pass
Recommended alternative - use prettier --check directly:
npx prettier --check "**/*.md"
npx prettier --check "**/*.yml"
Before Merging
Before merging a PR, always update it with latest main:
git checkout <branch-name>
git pull upstream main --rebase
git push -f
This prevents "branch is out-of-date with base branch" errors when merging.
IDE/LSP Setup
For proper code completion and analysis in editors like Visual Studio Code with clangd, generate compile_commands.json:
./scripts/generate-compile-commands.sh
This provides Qt include paths so the LSP can resolve types like QString, QProcess, etc.
Note: compile_commands.json is in .gitignore - regenerate after cleaning or re-configuring.
Related Skills
- qtpass-testing - For testing patterns and
make check
- qtpass-fixing - For bugfix workflow with tests
- qtpass-releasing - For release process