Skip to content

fix(rtd-sphinx): probe docs/sphinx, add a docs_dir input, and fail on a missing conf.py#14

Merged
ywatanabe1989 merged 2 commits into
mainfrom
fix/probe-docs-sphinx-conf
Jul 22, 2026
Merged

fix(rtd-sphinx): probe docs/sphinx, add a docs_dir input, and fail on a missing conf.py#14
ywatanabe1989 merged 2 commits into
mainfrom
fix/probe-docs-sphinx-conf

Conversation

@ywatanabe1989

Copy link
Copy Markdown
Contributor

The defect

The org reusable probed for conf.py at docs/, docs/source/ and docs/src/ only, and on a miss did:

echo "no sphinx conf.py (checked docs[/source|/src]) — skipping; PS-122 satisfied by workflow content"
exit 0

No repo in this org uses any of those three paths. So the gate matched nothing, exited 0, and reported the same green as a successful build. It is the "skipped counted as clean" failure at fleet scale, and because the skip is indistinguishable from a pass it self-conceals — nobody had reason to look.

scitex-agent-container already diagnosed this and refused to adopt the reusable because of it. From its own rtd-sphinx-build-on-ubuntu-latest.yml:

NOTE this job does NOT call the org reusable (scitex-ai/.github/.github/workflows/rtd-sphinx-build.yml). That reusable probes for conf.py at docs/, docs/source/ and docs/src/ ONLY — sac's lives at docs/sphinx/conf.py, so the reusable would have found nothing, printed "no sphinx conf.py — skipping" and exited 0. It would have converted this gate into a GREEN NO-OP. Consolidate only after teaching it that path.

This PR teaches it that path.

Measurement

Every conf.py in every default branch, enumerated via the git-trees API (not guessed paths), on 2026-07-22:

count
repos in org 74
docs/sphinx/conf.py 66
docs/sphinx/source/conf.py 1
no conf.py at all 8
any currently-probed path 0
repos calling this reusable 15

Cross-checked with a second, independent method (direct contents-API probes). Controls in the same batch: positive scitex-stats/README.md → 200; negative scitex-stats/docs/sphinx/conf_NO_SUCH_FILE_ZZZ.py → 404; second negative scitex-stats/docs/source/conf.py (a probed path) → 404. The probe discriminates.

The caller count needed its own control. A first pass grepping for the reusable's path anywhere in a workflow file returned 16 — and scored scitex-agent-container as a caller, which is impossible, since the only thing it contains is the refusal comment quoted above. That negative control voided the count. Re-run matching only real uses: lines: 15 callers, agent-container uses=0 mentions=1, scitex-stats uses=1. Both controls pass.

A prior sweep put this figure at 40. That is superseded: its artifacts contain no uses: references at all (0 rows carry uses: or scitex-ai/.github), so it cannot have been counting reusable-callers; 60 repos have a workflow file named rtd-sphinx*, and rows are recorded per branch (821 instances across main+develop vs 457 files on default branches, 54 repos appearing on both). Mention-matching plus branch double-counting, not calls.

Blast radius — say this out loud

15 repos are green-no-op today. After this merges they run a real sphinx-build. 51 more repos have docs at docs/sphinx/ and would join on adoption.

Adopting repos will see REAL failures where they previously saw green. That is the point of this PR, not a regression. A red that appears after this merge is a documentation build that was already broken and was being hidden by exit 0. Please do not read the new reds as "this PR broke our CI" — it removed the thing that was concealing them.

One measured detail makes that exact rather than a caveat: all 15 current callers have conf.py at docs/sphinx/. So none of them goes red for a missing conf.py — all 15 flip straight from green-no-op to a genuine build, and any red they show is a real doc-build failure that was invisible until now. The 8 repos with no docs at all are not callers; they would need required: false if they ever adopt.

The change

  1. docs/sphinx/conf.py added to the probe list.
  2. docs_dir is now an input (default docs/sphinx, the org convention — so the 66 repos on it need no with: block). The caller's directory is probed first; the legacy layouts remain as fallbacks so no existing repo breaks. Zero-input reusables are the documented root cause of the rtd-sphinx variant sprawl, so this is added as a real input rather than one more hardcoded path.
  3. A missing conf.py is a hard failure by default. required: false is the explicit, auditable opt-out for repos that genuinely ship no docs — and it is annotated ::warning:: and states NO DOCUMENTATION WAS BUILT, so an opted-out run can never be misread as a successful build. The failure names every path searched and prints a remediation block that is pasteable as printed:
::error::no sphinx conf.py found. Searched, in order:
::error::  docs/sphinx/conf.py
::error::  docs/conf.py
...
::error::To point the gate at your docs, pass the directory holding conf.py:
::error::    uses: scitex-ai/.github/.github/workflows/rtd-sphinx-build.yml@main
::error::    with:
::error::      docs_dir: path/to/your/docs

A gate that cannot fail is not a gate.

Tests

tests/test_rtd_sphinx_probe.py — 25 new tests, 38 in the suite, run by the existing self-test.yml.

These execute the real discovery shell, sliced out of the real workflow file, against real temp trees. No stubs, no network, no gh; the slice stops before the uv/sphinx install so nothing needs installing. A text-only test would assert that the right string is present, which is not the same as asserting the gate behaves.

Mutation table — all 10 red, restore green

# Mutation Result Caught by
M1 drop docs/sphinx/conf.py from CANDIDATES RED test_org_layout_is_found_without_docs_dir_pointing_at_it, …_via_fallback_succeeds
M2 drop $DOCS_DIR/conf.py (input ignored) RED 3 custom_docs_dir tests
M3 revert the miss branch to exit 0 RED test_missing_conf_py_fails_by_default
M4 flip required default to false RED test_required_defaults_to_true
M5 silence the opt-out (drop ::warning::) RED test_opt_out_is_annotated
M6 drop the searched-paths listing RED test_failure_names_every_path_it_searched ×4
M7 drop the opt-out remediation hint RED test_failure_hint_is_executable_as_printed
M8 move docs_dir default off the convention RED test_docs_dir_defaults_to_the_org_convention
M9 remove the docs_dir input entirely RED …_defaults_to…, test_docs_dir_input_is_declared
M10 drop the NO DOCUMENTATION WAS BUILT prose RED test_opt_out_states_that_nothing_was_built

M1 was GREEN on the first run — and that is the most important line here

The first harness run reported M1 green: deleting docs/sphinx/conf.py from the candidate list — the entire point of this PR — changed nothing, and the suite still passed.

The cause: the org-layout test passed docs_dir="docs/sphinx" explicitly, so $DOCS_DIR/conf.py matched first and masked the hardcoded fallback. The test could not fail. It was verifying the input while believing it was verifying the path.

That is precisely the defect class this PR exists to fix, reproduced inside the fix for it — a check that reports success without exercising the thing it claims to check. It was caught only because every property was mutation-tested rather than assumed.

The repair is a case that points docs_dir at a directory that does not exist, so a hit can only come from the fallback list. M1 has been red ever since.


Based on main (not develop) — org reusables resolve from the default branch.

…sing conf.py

The reusable probed conf.py at docs/, docs/source/ and docs/src/ only, and
`exit 0`d when it found none. Measured across scitex-ai on 2026-07-22 (74
repos, every conf.py path in every default branch via the git-trees API):
66 repos keep conf.py at docs/sphinx/conf.py, 1 at docs/sphinx/source/conf.py,
8 ship none, and ZERO use any of the three probed paths. 15 repos already
call the reusable, so in 15 of 15 the gate matched nothing and exited 0 --
green while building no documentation at all, and indistinguishable from a
real build.

Three changes:

* docs/sphinx/conf.py joins the probe list.
* docs_dir becomes an INPUT (default docs/sphinx). Zero-input reusables are
  the documented root cause of the rtd-sphinx variant sprawl; a repo with an
  unusual layout now overrides the input instead of forking the workflow.
* A missing conf.py is a HARD FAILURE by default, with `required: false` as
  the explicit opt-out for repos that genuinely ship no docs. The skip path
  is annotated ::warning:: and states that nothing was built, so it can never
  be read as a successful build. The failure names every path searched and
  prints a remediation hint that is pasteable as-is.

Tests execute the real discovery shell sliced out of the real workflow file
against real temp trees -- no stubs, no network. Mutation-proven: all 10
mutations (drop either candidate path, revert to exit 0, flip the required
default, silence the opt-out, drop the searched-paths listing, drop the
remediation hint, move the docs_dir default, remove the input, drop the
"NO DOCUMENTATION WAS BUILT" prose) turn the suite red; restoring turns it
green. An 11th test was added after the first harness run showed that
dropping docs/sphinx/conf.py from CANDIDATES left the suite GREEN -- the
org-layout test had passed docs_dir explicitly, masking the fallback.
@ywatanabe1989
ywatanabe1989 merged commit 3de8e9e into main Jul 22, 2026
1 check passed
@ywatanabe1989
ywatanabe1989 deleted the fix/probe-docs-sphinx-conf branch July 22, 2026 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant