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=valueonly — no comments, no empty lines, alphabetical (dotenv-linterUnorderedKeyrule). The workflow doescat .github/super-linter.env >> $GITHUB_ENVand$GITHUB_ENVrejects everything else. - Document the rationale for non-obvious vars in the workflow YAML (above the
catstep), not in the env file. GITHUB_*env vars set via$GITHUB_ENVdon't propagate to docker-based actions like super-linter. So you cannot override e.g.GITHUB_ACTIONS_ZIZMOR_CONFIG_FILEthis 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:
# Run a specific linter (e.g. zizmor) the way CI does
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/
# Or clang-format check
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 toref-pinin the config (version tags + dependabot cooldown). - commitlint 20 — picks up
.commitlintrc.jsfrom the repository root. - JSHint — only runs via Hound (not super-linter v8). Default ES5; we set
esversion: 11in.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
# 1. Make your changes
git add .
# 2. Run linter locally (this is the pattern)
act push -W .github/workflows/linter.yml -j build
# 3. Fix any issues
# 4. Push only when act passes
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
# Run linter locally
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:
# Run build workflow
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)
# Run docs workflow
act push -W .github/workflows/docs.yml
Reuse Compliance (.github/workflows/reuse.yml)
Check license headers and REUSE compliance:
# Run reuse check
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.
Run Doxygen Locally
doxygen Doxyfile
# No warnings = CI will pass (progress output is normal with QUIET = NO)
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:
/**
* @brief Brief one-line description.
* @param name Description of parameter.
* @return Description of return value.
*/
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@returnfor non-void functions - Signals with unnamed params: Qt signals also need named parameters and
@paramdocs @xyztypos: Doxygen treats unknown@wordas commands — use@brief Likenot@like
Coverage Report (optional)
Create a temporary Doxyfile override to enable XML for coverxygen (base Doxyfile may not have XML enabled):
# Generate XML docs (required for coverxygen)
cp Doxyfile Doxyfile.xml
echo "GENERATE_XML = YES" >> Doxyfile.xml
echo "XML_OUTPUT = xml" >> Doxyfile.xml
doxygen Doxyfile.xml
# Install and run coverxygen
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.
# Scan for secrets
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++)
# Check formatting
clang-format --style=file --dry-run src/main.cpp
# Apply formatting
clang-format --style=file -i src/main.cpp
Shfmt (Shell Scripts)
Formats shell scripts in scripts/ folder. Uses LLVM style (matches clang-format).
Installation:
# macOS
brew install shfmt
# Go
go install mvdan.cc/sh/v3/cmd/shfmt@latest
# Check formatting
shfmt -d scripts/*.sh
# Apply formatting
shfmt -w scripts/*.sh
Clangd (LSP Analysis)
Clangd provides deep static analysis via LSP. Requires compile_commands.json:
# Generate compile_commands.json (required for Qt headers)
./scripts/generate-compile-commands.sh
# Check a specific file for issues
clangd --check=src/gpgkeystate.cpp
Common diagnostics:
[performance-unnecessary-copy-initialization]- Useconst T&instead ofconst 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)
# Format markdown, YAML, JSON, etc.
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:
# Lint a specific file
npx -p textlint -p textlint-rule-terminology textlint --rule terminology AGENTS.md
# Lint every Markdown file in the repo (catches latent errors super-linter misses)
npx -p textlint -p textlint-rule-terminology textlint --rule terminology \
*.md scripts/*.md .github/**/*.md .opencode/skills/*/SKILL.md
# Auto-fix
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:
# Format all common file types
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
# Run prettier first
npx prettier --write README.md
# Then check again
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
.gitleaksignoreif truly non-sensitive
act Unknown Flag Error
Make sure act is installed and up to date:
# Check version
act --version
# Update if needed
brew upgrade act # or your package manager
CI Environment Variables
Some linters need secrets or tokens. In local act, these may not be available:
# Pass fake token for codecov
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:
# 1. Format files with prettier (always do this)
npx prettier --write "**/*.md" "**/*.yml"
# 2. Verify formatting passes (REQUIRED - catches linting issues)
npx prettier --check "**/*.md"
# 3. Run act linter (recommended before opening PR)
act push -W .github/workflows/linter.yml -j build
# 4. Update with latest main (if branch is behind)
git fetch upstream
git pull upstream main --rebase
# 5. Then push
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 --checkstep is sufficient for most cases - Trust that formatting is correct
- The real GitHub CI will pass
Recommended alternative - use prettier --check directly:
# This catches most linting issues without needing act
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:
# Generate compile_commands.json using bear
./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