Configuration templates shared across personal repositories.
mise.toml pins the pre-commit version for this repository. With mise installed, set up the tools and Git hooks:
mise install
mise exec -- pre-commit install.pre-commit-config.yaml is the canonical config for personal repositories. pre-commit has no config inheritance, so copy it into each new repository, together with .markdownlint-cli2.yaml, the markdownlint-cli2 config that disables MD013 (line length) and MD029 (ordered list item prefix).
cp ~/git/shared-config/.pre-commit-config.yaml ~/git/shared-config/.markdownlint-cli2.yaml .
pre-commit installdefault_install_hook_types in the config makes pre-commit install set up both the pre-commit and the commit-msg hook, so no extra flags are needed. Repositories that ran pre-commit install before the commit-msg hook was added need to run it once more.
After copying, Renovate (:enablePreCommit) keeps each repository's rev values up to date. Each rev is pinned to a commit SHA with a # frozen: <tag> comment, and Renovate updates both the SHA and the tag. A bare SHA without the comment is treated as a version and is not updated correctly, so keep the comment when adding a hook. pre-commit autoupdate --freeze writes the same format.
| Hook | Target |
|---|---|
| check-yaml | YAML syntax |
| end-of-file-fixer / trailing-whitespace | All files |
| actionlint | .github/workflows/*.yml |
| ruff-check / ruff-format | Python, including Bandit security rules (S) |
| biome-check | JavaScript / TypeScript |
| gitleaks | Secret detection |
| semgrep | Static analysis for security issues and bugs |
| trivyfs-docker | Dependency vulnerabilities and IaC misconfigurations (Trivy) |
| markdownlint-cli2 | Markdown |
| shellcheck | Shell scripts |
| conventional-pre-commit | Commit messages (commit-msg stage) |
Hooks with no matching files are skipped, so unused hooks can stay in place.
conventional-pre-commit rejects commit messages that do not follow Conventional Commits, such as feat: add login form or fix(api): handle empty payload.
It runs on the commit-msg stage, which has two consequences:
pre-commit run --all-files, including the CI workflow below, does not check commit messages. Enforcement happens locally at commit time, andgit commit --no-verifybypasses it- To check a message by hand, pass the file explicitly
pre-commit run --hook-stage commit-msg --commit-msg-filename .git/COMMIT_EDITMSGAllowed types default to build, chore, ci, docs, feat, fix, perf, refactor, revert, style, and test, and fixup!/squash! and merge commits pass as-is. Useful args per repository:
[feat, fix, docs, chore]narrows the allowed types (featandfixare always allowed)[--force-scope]requires a scope,[--scopes, api,client]restricts it[--strict]also rejectsfixup!/squash!and merge commits
semgrep scans staged files with the p/default ruleset and fails the commit on any finding (--error). Some details to keep in mind:
- The ruleset is downloaded from the Semgrep Registry on every run, so the hook needs network access and fails offline
argsreplace the hook's default args, so the config repeats--skip-unknown-extensions,--disable-version-check, and--quiet. Keep them when changingargs- The rulesets are named explicitly rather than using
--config auto, which requires sending metrics to Semgrep - To ignore a false positive, add a
# nosemgrep: <rule-id>comment on the reported line
Useful changes per repository:
- Add rulesets for the languages in use, e.g.
[--config, p/default, --config, p/python, ...] - Point
--configat a rules file in the repository, e.g..semgrep.yml, to run without network access - Add
.semgrepignoreto skip paths such as generated files or test fixtures
trivyfs-docker from mxab/pre-commit-trivy runs Trivy in the aquasec/trivy Docker image and fails the commit on any HIGH or CRITICAL finding. Some details to keep in mind:
- Docker must be running locally. GitHub-hosted Ubuntu runners have it preinstalled
- The hook scans the whole repository rather than the staged files, so it runs on every commit
- The vulnerability database is downloaded on the first run and when it goes stale, so the hook needs network access
- The cache is written to
.pre-commit-trivy-cachein the repository. Add it to.gitignore --scanners vuln,misconfigskips Trivy's secret scanner, since gitleaks already covers secrets- Trivy does not read
.gitignore, so--skip-dirs "**/node_modules"and--skip-dirs "**/.venv"keep it out of installed dependencies at any depth. Lockfiles andrequirements.txtare still scanned argsreplace the hook's default args, and the last one must be the path to scan (.)
echo ".pre-commit-trivy-cache/" >> .gitignoreUseful changes per repository:
- Widen
--severity, e.g.MEDIUM,HIGH,CRITICAL, or add--ignore-unfixedto skip vulnerabilities with no fix - Add more
--skip-dirs <dir>before.to skip paths such as test fixtures - Add
.trivyignorewith one CVE or check ID per line to ignore a false positive
ruff-check runs with --extend-select S, which adds the flake8-bandit rules, Ruff's port of Bandit, to whatever rules the repository's Ruff config selects. The flag extends rather than replaces the selection, so select and extend-select in pyproject.toml or ruff.toml keep working.
- To ignore a false positive, add a
# noqa: <rule>comment on the reported line, e.g.# noqa: S603 S101flags everyassert, including those in pytest tests. Ignore it for tests in the repository's Ruff config:
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"]- Change the
check-yamlargs to[--unsafe]for YAML with custom tags, such as CloudFormation templates - Add
exclude:for files that cause false positives, such as generated files (e.g.exclude: ^src/content/for markdownlint-cli2)
.github/workflows/pre-commit.yml is a reusable workflow that runs all hooks against all files. It installs the same pre-commit version as mise.toml, and Renovate updates both through customManagers:githubActionsVersions in the shared preset. When the calling repository has a mise.toml, the workflow also sets up mise and installs the tools it pins, and uses the pre-commit from mise.toml if it is listed there. Otherwise it installs pre-commit with pip. Call it from each repository with .github/workflows/pre-commit.yml:
name: "repo - Pre-commit"
on:
pull_request:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
pre-commit:
uses: iwstkhr/shared-config/.github/workflows/pre-commit.yml@mainTo get a Slack message when pre-commit fails, pass the Slack secrets described in Slack notifications. The workflow calls slack-notify.yml only on failure, and skips it when either secret is missing, such as in pull requests from forks:
jobs:
pre-commit:
uses: iwstkhr/shared-config/.github/workflows/pre-commit.yml@main
secrets:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}Runs in this repository notify the same way once these secrets are set here.
This repository is public, so any repository can call the workflow without changing its Actions access settings.
.github/workflows/slack-notify.yml is a reusable workflow that posts a workflow result to a Slack channel with the official Slack GitHub Action (chat.postMessage). Renovate keeps its pinned version up to date. The message links to the calling run and shows the repository, branch, commit, and actor, color-coded by result.
- Create a Slack app with the
chat:writebot token scope and install it to the workspace - Invite the app to the target channel (
/invite @<app name>) - Store the bot token (
xoxb-...) and the channel ID (e.g.C0123456789) as secrets in each calling repository
Add a job that runs after the jobs to report, and pass their result as status:
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo build
notify:
needs: build
if: ${{ always() }}
uses: iwstkhr/shared-config/.github/workflows/slack-notify.yml@main
with:
status: ${{ needs.build.result }}
secrets:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}- Map each secret on the right-hand side to the name used in the calling repository. When the names already match,
secrets: inheritworks too - Use
if: ${{ failure() }}to notify only on failure - When
needslists several jobs, combine their results, e.g.status: ${{ contains(needs.*.result, 'failure') && 'failure' || 'success' }}
| Input | Required | Description |
|---|---|---|
status |
Yes | success, failure, or cancelled get a matching color and label; any other value is shown as a warning |
message |
No | Plain text shown under the title. Slack markup such as links and mentions is escaped |
If the Slack API returns an error, such as not_in_channel or invalid_auth, the job fails and the error appears in the log.
.github/workflows/ai-review.yml is a reusable workflow that reviews a pull request with Claude Code GitHub Actions, Codex GitHub Action, and Cursor CLI when the ai-review label is added. When the caller also triggers on synchronize, pushing commits to a pull request that has the label runs the review again. Claude posts a tracking comment with progress and a summary, plus inline comments on specific issues. Codex and Cursor each run in a separate job and post their review as one comment. Renovate adds the label to major updates and to minor updates of npm packages from 0.x versions (see packageRules), so those PRs are reviewed automatically.
- Install the Claude GitHub App on the calling repository
- Store one of these as a secret in the calling repository to enable the Claude review. Without either, the Claude review is skipped:
CLAUDE_CODE_OAUTH_TOKEN: generated withclaude setup-token(uses a Claude subscription)ANTHROPIC_API_KEY: an Anthropic API key
- Optionally store
OPENAI_API_KEY(an OpenAI API key) as a secret to enable the Codex review. Without it, the Codex review is skipped - Optionally store
CURSOR_API_KEY(an API key from the Cursor dashboard) as a secret to enable the Cursor review. Without it, the Cursor review is skipped - Create the
ai-reviewlabel in the calling repository
Add .github/workflows/ai-review.yml to each repository:
name: "repo - AI Review"
on:
pull_request:
# Remove synchronize to skip reviewing again on each push
types: [labeled, synchronize]
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
jobs:
ai-review:
uses: iwstkhr/shared-config/.github/workflows/ai-review.yml@main
secrets:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}- The caller must grant the permissions above, because a reusable workflow cannot exceed them
- Adding any other label or pushing to a pull request without the label does not start a review, and pull requests from forks are skipped because they cannot read the secrets
- Removing and adding the label again runs a new review, cancelling one still in progress
- With
synchronize, each push to a pull request that has the label runs all reviews again, cancelling ones still in progress. Renovate rebases count as pushes - Each review run posts a new Claude tracking comment and new Codex and Cursor comments, so reviewing on push adds cost and comments. Remove
synchronizeif you do not need it - The Claude, Codex, and Cursor reviews run in parallel as the
Claude,Codex, andCursorjobs - The Codex and Cursor jobs get a read-only token, and separate
Codex commentandCursor commentjobs post their reviews. The agents read untrusted pull request content, and the Cursor CLI comes from an unpinned installer, so a prompt injection or a compromised installer cannot write to the repository - The Claude job needs write permissions because claude-code-action posts its comments itself. Its tools are limited to reading the pull request, fetching web pages (for release notes), and commenting on the pull request
- Adding several labels at once starts one run per label, and pushing to a pull request without the label also starts a run. The jobs in runs that do not review are skipped with names ending in
(not requested), so they do not hide the review results in the pull request checks
| Input | Default | Description |
|---|---|---|
label |
ai-review |
Label that triggers the review |
allowed_bots |
renovate[bot] |
Comma-separated bot usernames allowed to trigger the review by adding the label. Users need write access to the repository. Applies to Claude and Codex. The Cursor review runs regardless of who added the label |
enable_claude |
true |
Review with Claude Code. The review is also skipped when neither CLAUDE_CODE_OAUTH_TOKEN nor ANTHROPIC_API_KEY is set |
enable_codex |
true |
Review with Codex. The review is also skipped when OPENAI_API_KEY is not set |
enable_cursor |
true |
Review with Cursor. The review is also skipped when CURSOR_API_KEY is not set |
extra_prompt |
"" |
Additional instructions appended to all review prompts, e.g. Write the review in Japanese. |
Runs in this repository review the same way once one of the secrets is set here.
renovate-preset.json is a shared Renovate preset. Repositories apply the common dependency update rules by extending it in their Renovate config (e.g. renovate.json):
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>iwstkhr/shared-config:renovate-preset"]
}Renovate reads the preset from the default branch, so changes take effect in every repository once they are merged into main.
| Preset | Purpose |
|---|---|
config:recommended |
Renovate's recommended settings |
helpers:pinGitHubActionDigests |
Pin GitHub Actions to digests |
:enablePreCommit |
Enable updates for pre-commit hooks |
customManagers:biomeVersions |
Detect Biome versions with a custom manager |
customManagers:githubActionsVersions |
Update _VERSION environment variables marked with # renovate: comments in GitHub Actions workflows |
- labels: Add the
dependencieslabel to created PRs - timezone:
Asia/Tokyo - dependencyDashboard: Enable the Dependency Dashboard issue
- minimumReleaseAge: Wait 7 days after a release before updating
- rebaseWhen: Rebase only when there are conflicts
Creates a lock file maintenance PR before 5:00 AM (Asia/Tokyo) every Monday and automerges it.
| Condition | Behavior |
|---|---|
| Major updates | Add the breaking-change and ai-review labels alongside dependencies and request review from iwstkhr when the PR is created (no automerge). The ai-review label starts the AI review |
| Minor / patch updates | Group as non-major dependencies and automerge |
Minor updates of npm packages from 0.x versions |
Since semver allows breaking changes in 0.x minor releases, open a separate PR (not grouped), add the ai-review label alongside dependencies, and request review from iwstkhr (no automerge). The breaking-change label is not added because these updates are not necessarily breaking. Other managers, such as pre-commit hooks, follow the minor / patch rule above |
| GitHub Actions pin / digest updates | Automerge |
.github/workflows/renovate-validate.yml validates renovate-preset.json and renovate.json with the Renovate config validator when either file changes. To run the same check locally:
npx --yes --package renovate -- renovate-config-validator --strict --no-global renovate-preset.json renovate.json--no-global validates the files as repository config rather than self-hosted global config, and --strict also fails when an option needs migration. The validator does not fetch presets referenced in extends, so a wrong preset name only shows up on the Dependency Dashboard after Renovate runs.