Three defects in `sphinx-alt-text-validator`
Maintainers usually reply within 1 day
@kesavan272006 is already working on this.
Since Sep 24, 2026.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 75/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- python
- Domain
- ci-cd, documentation, tooling
Research direction
The issue points to three specific defects in sphinx-alt-text-validator. Start by examining the scripts/image-tester directory and the sphinx_alt_text_validator/verify_images.py module. Look for the is_image function, the validate_images loop logic, and the main() function's glob pattern. The fixes involve adding '.. figure::' to the directive check, handling end-of-file validation, and extending the file extension glob. Test changes by running the validator on sample RST and Python files with and without alt text.
Written by the indexing model from the issue text.
Description
While adding an alt-text check to qiskit-fermions' own CI (see https://github.com/Qiskit/qiskit-fermions/pull/358), I tried to adopt scripts/image-tester rather than write another copy — the tool exists precisely so the nine API repos don't each maintain one (#2445: "instead of duplicating the same script 9 times").
I couldn't use it, for three independent reasons. Each is reproducible against the code at main, and two of them also affect Qiskit's vendored ancestor tools/verify_images.py.
Reporting them together because their combined effect is worse than any one alone: on a repository like ours the tool exits 0 having validated nothing, which is indistinguishable from a clean pass.
1. .. figure:: is not recognised
is_image() matches only .. image:: and .. plot:::
def is_image(line: str) -> bool:
"""Determine if a line is an image"""
return line.strip().startswith((".. image::", ".. plot::"))
.. figure:: is an image directive that takes :alt: exactly like .. image::, so a figure without alt text passes silently:
>>> from sphinx_alt_text_validator.verify_images import validate_images
>>> validate_images(".. figure:: images/overview.svg\n\n A caption.\n")
[]
This is not hypothetical. qiskit-fermions was asked by the platform linter to add alt text to one image, and the fix (Qiskit/qiskit-fermions#349) converted that .. image:: to a .. figure:: with :alt: — because a figure is the right directive when the image has a caption. The result is that the tool intended to catch this class no longer sees our only non-plot image at all. After that change our tree contains 116 .. plot:: + 1 .. figure:: + 0 .. image::, so the blind spot covers 100% of the images that aren't plots.
Suggested fix, matching the existing style:
return line.strip().startswith((".. image::", ".. figure::", ".. plot::"))
is_valid_image needs no change: :nofigs: is plot-only, and a figure with :alt: already passes.
2. A directive at end of file is never checked
validate_images decides whether a directive was valid on a later loop iteration — when it encounters a line that is not an option. If the directive's option block runs to the end of the file, that iteration never happens and the directive is dropped:
>>> validate_images(".. image:: a.png\n") # nothing after it
[]
>>> validate_images(".. image:: a.png\n\ntext\n") # something after it
['- Error in line 1: .. image:: a.png']
Same input, same defect, different verdict depending only on whether the file happens to end there. A docstring whose last line is a .. plot:: — or an .rst file ending in an image — is exempt by accident.
The fix is to run the same validity check once more after the loop, when image_found is still set.
3. main() only globs **/*.py, and the glob isn't overridable
files = glob.glob(f"{args.folder}/**/*.py", recursive=True)
The CLI exposes --folder and --skip, so there is no way to reach any other file type.
For repositories whose docs are reStructuredText this means the tool cannot be pointed at them at all. In qiskit-fermions the 117 image directives live in .rst (113), .rs (2, PyO3 docstrings) and .pyi (2, generated stubs) — and zero in .py. Run as documented, sphinx-alt-text-validator -f docs scans conf.py and conftest.py, finds no directives, and prints ✅ All images have alt text.
The underlying validate_file is fine on other extensions — I verified it returns correct results on our .rst and .rs files. Only the entry point's glob is the problem.
Suggested fix: an --extensions option defaulting to py to preserve current behaviour, or glob a set covering py, pyi, rst and rs. Adding rs would also let this cover Rust-based projects, which is where Qiskit's own C API docstrings now live.
Why this matters beyond one repo
Defects 1 and 3 are inherited rather than introduced here — Qiskit/qiskit/tools/verify_images.py has the identical *.py glob and the identical missing figure, and Qiskit still runs that copy rather than this package. So the shared tool currently has no adopter that would have caught these, and the duplication #2445 set out to remove is still in place.
Separately, scripts/js/commands/checkMarkdown.ts reasons about exactly this division of labour:
"If it happens, it would probably be a backport from latest or dev, and the linter in the API repo should catch it. If an image is missed by the API repo's linter, it will still have an alt text defined, although it won't be very useful. That's because Sphinx auto-generates alt text."
That assumption holds only if the API-repo linter actually sees the repo's images. For an .rst-based project today, it doesn't.
What we did meanwhile
We implemented the check in qiskit-fermions (tools/lint_docs.py, wired into tox -e lint), covering image/figure/plot across .rst, .rs, .py and .pyi, with :alt: required and :nofigs: accepted for plots — the same policy as here.
Happy to open a PR for any or all of the three fixes if that's useful; defects 1 and 2 are a few lines each. We'd rather converge on the shared tool than keep a parallel implementation, so if the glob becomes configurable we would look at switching to it.
- Dominant language
- Jupyter Notebook
- Stars
- 108
- Forks
- 236
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 73
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Qiskit/documentation
-
content 📄
Difficulty 1/5 1-3 hours Newbie friendliness 68/100
Qiskit/documentation#5808 ·
Maintainers usually reply within 1 day
-
Document resource unit fields in service instance API guidePossibly taken @AryanSilver24 claimed this 3 days ago. Opencontent 📄
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Qiskit/documentation#5790 ·
Maintainers usually reply within 1 day
-
Add new images to ParityQC guidePossibly taken @abbycross claimed this 23 days ago. Opencontent 📄
Difficulty 1/5 Under an hour Newbie friendliness 70/100
Qiskit/documentation#5686 · 1 assignee ·
Maintainers usually reply within 1 day
-
content 📄
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Qiskit/documentation#5673 · 2 comments ·
Maintainers usually reply within 1 day
-
Update `ignore.ts` in dev docsPossibly taken @mahimagalla claimed this 9 days ago. Openinfra 🏗️ needs triage 🤔
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Qiskit/documentation#5024 ·
Maintainers usually reply within 1 day
All issues in Qiskit/documentation
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
bioconda/bioconda-recipes#70401 ·
Maintainers usually reply within 2 days
-
a11y data import fails with "missing --authors parameter" (npm drops flags in `bin/configure.js`)Open
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
-
agentic-workflows Needs: Triage :mag:
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
amnezia-vpn/amnezia-client#3297 · 1 comment ·
Maintainers usually reply within 1 day
-
bug o&m
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
GSA/datagov-harvester#1024 ·
Maintainers usually reply within 1 day