Skip to content

Python generator leaves stale modules behind, and the Python CI drift step cannot see them #757

Description

@jeremy

Found while closing a Codex review finding on #752. Two halves of one defect; the second is the one that reaches CI.

1. The generator does not delete outputs it no longer produces

python/scripts/generate_services.py writes the current modules and rewrites __init__.py, and stops there. The other three generators all sweep first:

generator stale handling
typescript/scripts/generate-services.ts:1736-1741 fs.unlinkSync any .ts not in the generated set
ruby/scripts/generate-services.rb:344-353 deletes any *_service.rb carrying @generated and not regenerated
kotlin/generator/.../Main.kt:65 wipes the services dir before writing
python/scripts/generate_services.py nothing

So removing or renaming a service in the Python mapping leaves the old module on disk, and make py-generate-services will never clear it.

2. The Python CI drift step is blind to the leftover

The local script and the CI step are not the same check.

scripts/check-python-service-drift.sh regenerates into a tmpdir and diff -rqs against a copy of the committed tree. That reports Only in <committed>: fanfares.py and fails. Verified:

$ printf '# @generated stale module\n' > python/src/basecamp/generated/services/fanfares.py
$ make py-check-drift
ERROR: Generated services are out of date. Run 'make py-generate-services'
Only in /var/folders/.../services_committed: fanfares.py
REAL_EXIT=2

The Python CI job (.github/workflows/test.yml, "Check generated code drift") does not run that script. It regenerates in place and tests git status --porcelain -- src/basecamp/generated/. A stale file that is already committed produces no diff — nothing rewrites it, nothing deletes it — so there is nothing for git status to report. Verified in a throwaway repo reproducing the sequence:

PORCELAIN_LINES=0  <-- CI passes
stale file still present: fanfares.py  keep.py

A Python mapping removal therefore reaches main with green CI. Only a developer running the full local make would see it.

This also trips the house rule that drift gates must regenerate out-of-tree rather than in-place (an in-place regeneration mutates the working tree it is judging).

Remedy

  1. Give generate_services.py the sweep the other three have. It must skip _base.py and _async_base.py, which are hand-written infrastructure living under generated/ by exception (AGENTS.md Hard Rule 1) — Ruby's @generated-marker guard is the safest pattern to copy.
  2. Point the CI step at scripts/check-python-service-drift.sh so CI and make run the same check, instead of a weaker in-place variant.

Already worked around, but only for one reader

scripts/check-service-inventory-parity (#752) reads Python from its generated __init__.py barrel rather than the directory, precisely because the barrel is rewritten whole and cannot name a corpse. That is a workaround in one gate, not a fix.

python/tests/test_client.py::TestGroupedClientAccessorInventory (#748) still enumerates the directory — but it fails safe: a stale module makes it demand an accessor for a service that no longer exists, which is red, not green. Worth leaving as-is until the generator is fixed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpythonPull requests that update the Python SDK

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions