qtpass-linting

star 1.1k

QtPass CI/CD workflow - run GitHub Actions locally with act, linters, formatters

IJHack By IJHack schedule Updated 6/16/2026

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:

# 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 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

# 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 @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):

# 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] - 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)

# 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:

  1. Use generic test values (not like "ABC123DEF456")
  2. Add to .gitleaksignore if 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 --check step 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
Install via CLI
npx skills add https://github.com/IJHack/QtPass --skill qtpass-linting
Repository Details
star Stars 1,137
call_split Forks 170
navigation Branch main
article Path SKILL.md
More from Creator