This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A GitHub Action that spell checks Python, Markdown, and Text files using PySpelling. The action runs inside a Docker container (jonasbn/github-action-spellcheck) and is published to GitHub Marketplace and DockerHub.
The action has three core components that work together:
-
entrypoint.sh— the DockerENTRYPOINT. Reads action inputs (passed asINPUT_*env vars), discovers the spellcheck config file, builds and runs thepyspellingCLI command. Handles source file parsing (space/quote-delimited), spell checker selection (aspell/hunspell), and optional artifact output. -
pwc.py— a Python helper that validates whether a candidate file matches thesources:glob patterns in the PySpelling YAML config. Called per-file fromentrypoint.shto decide whether to skip files that don't match the configured patterns. -
Dockerfile— builds frompython:3.14.x-slim-trixie, installs aspell/hunspell and all supported language packages viaapt-get, then installs Python dependencies fromrequirements.txt. The working directory inside the container is/tmp(the checked-out repository is mounted there).
Action inputs (action.yml) map to env vars in the container:
config_path→INPUT_CONFIG_PATHsource_files→INPUT_SOURCE_FILEStask_name→INPUT_TASK_NAMEoutput_file→INPUT_OUTPUT_FILEspell_checker→INPUT_SPELL_CHECKERskip_dict_compile→INPUT_SKIP_DICT_COMPILE
Config file discovery order (first match wins): .spellcheck.yml, .spellcheck.yaml, spellcheck.yml, spellcheck.yaml, .pyspelling.yaml, .pyspelling.yml.
docker build -t jonasbn/github-action-spellcheck:local .# Using Docker
docker run -it -v $PWD:/tmp jonasbn/github-action-spellcheck:local
# Against a config at a non-default path (skips discovery order)
docker run -it -v $PWD:/tmp -e INPUT_CONFIG_PATH="path/to/config.yaml" jonasbn/github-action-spellcheck:local
# Using pyspelling directly (requires local install)
pyspelling -c spellcheck.yamlThe published image can lag requirements.txt (Docker layer caching, manual DockerHub releases via scripts/build.pl). To check the real installed versions rather than trusting the file:
docker pull jonasbn/github-action-spellcheck:0.x.x # tag from action.yml
docker run --rm --entrypoint pip3 jonasbn/github-action-spellcheck:0.x.x listTests use Bats and require the Docker image built as :local:
bats test/test.batsNote: All Bats tests use
-it(interactive TTY). When run non-interactively (e.g. from a script or CI without a terminal), tests 1–5 fail with "cannot attach stdin to a TTY-enabled container" — this is a pre-existing limitation, not a regression. Usedocker run -v $PWD:/tmp jonasbn/github-action-spellcheck:localas the reliable non-interactive smoke test.
Run a single test:
bats --filter "can do basic run using Docker image without any input files" test/test.bats# Markdown linting
markdownlint '**/*.md'
# Shell script linting
shellcheck entrypoint.sh
# Perl linting (scripts in scripts/ and prototypes/)
perlcritic scripts/build.plpre-commit run --all-filesHooks enforce: trailing whitespace, end-of-file newlines, YAML/JSON validity, sorted wordlist.txt, and markdownlint.
requirements.txt is generated from requirements.in via pip-tools — do not hand-edit requirements.txt directly, since manual edits to individual transitive pins (e.g. bumping bracex without checking what wcmatch actually requires) is what caused issue #378.
pip install pip-tools
# Bump everything to the latest mutually compatible versions
pip-compile requirements.in --output-file=requirements.txt --strip-extras --upgrade
# Bump only specific packages, leaving all other pins untouched
pip-compile requirements.in --output-file=requirements.txt --strip-extras --upgrade-package <name>Run this with the same Python minor version as the Dockerfile's base image (check FROM python:3.14.x-slim-trixie) so resolved versions match what actually installs in the container. After regenerating, rebuild the local image and rerun the smoke test to confirm.
spellcheck.yaml— this repo's own PySpelling config (checks**/*.md, excludesvenv/,CLAUDE.md, and.claude/**/*.md)wordlist.txt— custom dictionary for this repo's spellcheck (must stay sorted; pre-commit enforces this)constraint.txt— pip version constraints for reproducible buildsrequirements.in— direct Python dependencies (pyspelling,pymdown-extensions); source of truth forrequirements.txtrequirements.txt— fully pinned dependency set (direct + transitive) installed in the Docker image, generated fromrequirements.inviapip-compile— do not hand-editdictionary.dic— aspell dictionary file (binary format, used during image build)examples/— example PySpelling configurations for Markdown, Python, and plain text
- GHCR (CI): uses
GITHUB_TOKEN— no PAT required, never expires. - DockerHub (local): uses your local
docker loginsession. Verify it is valid before runningbuild.pl(docker login).
The Docker image is published via the docker-build.yml workflow on push to master or on any tag. Tags trigger semver-tagged image builds. The action.yml points to a pinned image version (docker://jonasbn/github-action-spellcheck:0.x.x) and must be updated manually when releasing a new version.
Always use the release-prep skill (.claude/skills/release-prep/SKILL.md) when preparing a release. The skill has disable-model-invocation: true — it cannot be called via the Skill tool; follow its steps manually. Key points:
- Update
action.ymlimage tag and alluses: rojopolis/spellcheck-github-actions@X.Y.Zreferences inREADME.md. - Do not update
@v0references inREADME.md— these are floating major-version tags and intentionally unpinned. - Verify a
CHANGELOG.mdentry exists for the version before proceeding. - Open a PR for these changes — do not commit directly to
master. - After the PR merges, run
perl scripts/build.pl $VERSIONto tag and push to GitHub and DockerHub.
- Dependabot may propose Python pre-release images (e.g.
3.15.0b2—b= beta). Close these by commenting@dependabot ignore this minor versionon the PR. - The pip ecosystem entry in
.github/dependabot.ymlusesversioning-strategy: lockfile-only, which expects arequirements.in(manifest) +pip-compile-generatedrequirements.txt(lockfile) pair. Dependabot will bump transitive pins inrequirements.txton schedule but won't touch the exact versions pinned inrequirements.in— bump those manually when a direct dependency (pyspelling,pymdown-extensions) needs a deliberate update.
- Inputs under
with:in workflow YAML examples must be indented 2 spaces relative towith:. This has been a recurring source of invalid examples. - Don't trust a reviewer's (e.g. Copilot's) "this is invalid YAML" claim at face value — verify with
python3 -c "import yaml; yaml.safe_load(open('README.md').read())"on the extracted block. Same-indentation block sequences (list items aligned with their parent key) are valid YAML even though they look wrong; the real issue is usually inconsistency with this file's convention, not invalidity.
- Branches behind open PRs may receive direct pushes from the user (e.g. resolving a merge conflict via the GitHub UI) while a session is in progress. A local
git pushcan be rejected as a result —git fetchand merge (don't force-push) before retrying.