check-self-contained-captions.yml
Diff-scoped, blocking check that flags a figure or table caption whose first sentence only points to another div. The lab’s rule (Ezra, 2026-10-26) is that a caption states what the float shows, so it stands alone. A cross-reference may support the claim and never replaces it. This implements gha#1237.
A caption is the last paragraph of a #fig- or #tbl- div, a one-line #| fig-cap: or #| tbl-cap: chunk option, or a pipe-table caption line : Caption {#tbl-x}. Its first sentence is flagged when it holds a cross-reference and, with the reference, inline code and link URLs removed, it has fewer than four words or ends on a word that needs a complement (“of”, “at”, “the”, …). A trailing “, as in @x” or “(see @x)” is support, not a defect. A long first sentence (eight words or more) may end on “of @x” and still say what the float shows. A table caption is a noun-phrase title, so “Estimated coefficients for ?@eq-x” passes. Only a first sentence under three words is flagged there. “The symmetry of ?@exm-logistic-symmetry.” is flagged. “The logistic function at -2 is as high as one minus its value at 2 (?@exm-logistic-symmetry).” passes.
A justified exception carries a reason: an HTML comment <!-- caption-ref: reason --> inside the div exempts it. Use paths-ignore to exempt whole paths.
Scope
Only a float that holds a line a pull request adds is reported, so existing captions are never reflagged. With no base-ref, or when the diff cannot be computed, the check skips with a warning instead of scanning the whole tree. Set whole-tree: true to measure how many existing captions would be flagged.
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
globs |
string | '*.qmd' |
Space-separated git pathspecs of tracked files to check (recursive by default). |
paths-ignore |
string | '' |
Comma- or newline-separated glob patterns (relative paths) to skip. Supports *, ? and recursive **. |
fail |
boolean | true |
Fail the workflow when a caption only points to another div. Set false for warning annotations only. |
whole-tree |
boolean | false |
Scan every tracked file in full instead of only a PR’s added lines. |
python-version |
string | '3.x' |
Python version to set up. |
runs-on |
string | empty (CI_RUNNER variable, else ubuntu-latest) |
Runner label for the job. Use a self-hosted label only for a private repository; never for a public one, whose pull requests can run arbitrary code on it (#1167). |
Permissions
Only contents: read (the default), so no permissions: block is needed.
Example
# Copy to .github/workflows/check-self-contained-captions.yml in your repo.
# Diff-scoped check: flags a caption that only points to another div when a PR
# adds a line to its float. Say what the float shows, or add
# <!-- caption-ref: reason --> in the div.
# Blocking by default.
# Only contents:read is needed (the default), so no permissions block is required.
name: Check self-contained captions
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
jobs:
check:
uses: Morrison-Lab/gha/.github/workflows/check-self-contained-captions.yml@v3
# with:
# globs: '*.qmd' # pathspecs to check (recursive)
# paths-ignore: 'chapters/outtakes/**' # skip known-noisy paths
# fail: true # set false to warn only (defaults to true)
# whole-tree: true # scan every file, not just a PR's additionsSee examples/check-self-contained-captions.yml for the full caller stub.