Repository navigation
fix(generate client): generate every language before writing the library (#1163) - #1169
Merged
Merged
Conversation
…ary (#1163) A failed run deleted the library's generated tree before the companion ran: GenerateClientLibrary rewrote contract/ first, then each language wiped its gen/ (_gen/ and the facades for Python) and only afterwards asked the companion to generate. When the companion failed, the library was left without bindings. Builds older than #1128 also wiped every module at the Python package root, including hand-written ones. Every language now runs the companion into scratch directories first (stageLanguage). contract/ and each language directory are written only once all of them have succeeded (stagedLanguage.apply), with the existing ownership rules unchanged: the wipe still spares hand-written files and scaffolds. This is how `generate proto` already stages. OpenAPI clients stage the same way, generating against a copy of the library's go.mod so the import paths match. The buf/validate half of #1163 is already fixed at its owner: core 15188366 gives Python's buf.yaml the dependencies every other language has, and main carries it since #1128 moved core to 88f4434c. The CLI that reported the issue predated that pin. A new companion-gated test holds the Python sources path to it: a proto importing buf/validate generates and its bindings import. Also fixes two pre-existing test problems: assertPythonImports ran "proto:<tag>" without the registry, and TestGenerateClientPythonMapsUpstreamProtoModules still expected option-only imports that companion 0.0.17's Python plugin drops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
antoinetoussaint-byte
added a commit
that referenced
this pull request
Oct 26, 2026
Resolves pkg/generators/client.go against #1169's staged generation: the version stamp runs after each staged language is applied, and a library with no version is refused before anything is staged, so the library is untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 26, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1163.
What was wrong
Two defects, each with its own owner.
1. A failed generation deleted the library's generated tree.
GenerateClientLibraryrewrotecontract/first. Then, per language, it wiped the generated content (gen/, the TypeScriptsrc/, the Python_gen/and facades) and only afterwards asked the companion to generate. When the companion failed, the library was left without bindings. Builds before #1128 also removed every*.pyat the Python package root except__init__.py. That is howauthority_client/work_context.pywas lost in the issue: the CLI that reported it predates #1128.2.
buf/validatedid not resolve for a Python client from sources. The proto companion's Pythonbuf.yamltemplate listed googleapis alone, while Go, TypeScript and Rust also list protovalidate. This is already fixed at its owner: codefly-dev/core 15188366 ("a Python client from sources resolves the shared protos every other language does"). CLImaincarries it since #1128 moved core to88f4434c. The CLI in the issue was an earlier build, the one that picked companion 0.0.16. No second mechanism is added here. This PR only adds a test that holds the CLI's Python sources path to the fix.The fix
pkg/generatorsnow separates generating from writing:stageLanguageruns the companion for every entry of every requested language into scratch directories. Nothing under--outputis touched.contract/. It then callsstagedLanguage.applyper language, which runs the existingwipe*/merge*/finalize*functions unchanged. The rules for what survives a successful run (hand-written modules at the Python package root, scaffold files written once) are the same as before. Staging only decides when they run.go.mod, so the staged copy generates against a copy of the library's owngo.mod, or the scaffold's when the library has none.Reused: the existing ownership rules (
wipePythonLibrary's notice-based facade detection,writeFileIfMissing,ensurePythonDependencies), the per-entry scratch directory the protobuf path already had, and the staging modelgenerate protouses. Not built: no copy of the whole library, no directory swap, no new ownership list.Also fixed, pre-existing, found while running the gated suite:
assertPythonImportsran the image asproto:<tag>without its registry, so every Python import check failed withdocker.io/library/proto not found.TestGenerateClientPythonMapsUpstreamProtoModulesstill expectedfrom buf.validate import ...,from google.api import ...and a"protovalidate"dependency. The Python plugin of companion 0.0.17 (core 844a0e09) drops imports used only for an option. The CLI's own comment ongoogleapisPyPIalready says so. The test now asserts that.make lintis clean.Tests and evidence
TestAFailedGenerationLeavesTheLibraryUntouched(pkg/generators)_gen/and facade wiped)TestRegeneratingAPythonLibraryKeepsWhatItDidNotWritestagedLanguage.applyTestGenerateClientFailureLeavesLibraryUntouchedCODEFLY_GENERATE_QUALIFY=1work_context.pyandtests/added, then a run whose sources import a missing file: the library hashes byte for byte the sameTestGenerateClientPythonFromSourcesResolvesProtovalidateCODEFLY_GENERATE_QUALIFY=1_gen/buf, and imports in the companion. Against a core copy with the pre-15188366 Python template it fails with the issue's exact error:imported file does not exist…runBuf: cannot update buf: process exited with code 100TestGenerateClientPythonMapsUpstreamProtoModulesCODEFLY_GENERATE_QUALIFY=1TestGenerateClient*CODEFLY_GENERATE_QUALIFY=1The gated tests ran locally on macOS arm64 with Docker (OrbStack) and companion
ghcr.io/codefly-dev/proto:0.0.17.End to end against codefly-dev/authority
feat/installation-scoped-role-grantswith this build:codefly generate client --from authority/local/grpc --language python --no-facade --name authority-client --forcesucceeds. It regenerates_gen/and leavesauthority_client/work_context.pyandtests/in place.Classification
Fix, at the place that owns each behaviour. When the library is written is decided in
pkg/generators. The Pythonbuf.yamldependencies belong to core's proto companion and were fixed there (15188366).Not verified
codefly add service, beforegenerate client). The error iscontainer recovery marker does not belong to this process or its launching parent. Main fails identically (checked onTestGenerateClientFromContractsDirandTestGenerateClientIsIdempotent). Each passes alone on this branch. That fixture problem is not addressed here. No CI job provisions the companion (pkg/conformance/matrix.jsonrowlinux-amd64-docker-generate), so CI does not run the gated tests.go buildis still left written. The compile check needs the library's own module and hand-written packages, so it runs after apply. User files are never removed by it; the docs say so.--from, beforegenerate clientreaches the library. The in-companion failure path is covered by the gated test, not by an authority run.🤖 Generated with Claude Code