# Qtpass Linting

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

- Skill: `ijhack/qtpass-linting` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ijhack/qtpass-linting`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ijhack/qtpass-linting/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- License: GPL-3.0-or-later
- Author: ijhack (https://skillmd.com/u/ijhack)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ijhack/qtpass-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:

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

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

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

```bash
# 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)

```bash
# Run docs workflow
act push -W .github/workflows/docs.yml
```

### Reuse Compliance (.github/workflows/reuse.yml)

Check license headers and REUSE compliance:

```bash
# 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.

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

```bash
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`:

```cpp
/**
 * @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):

```bash
# 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.

```bash
# 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++)

```bash
# 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:**

```bash
# macOS
brew install shfmt

# Go
go install mvdan.cc/sh/v3/cmd/shfmt@latest
```

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

```bash
# 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)

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

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

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

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

```bash
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:

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

```bash
# 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:**

```bash
# 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:**

```bash
# 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:**

```bash
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`:

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

