Repository navigation
Conversation
A module's main object takes the module's name and isn't namespaced, so a module named `directory`, `container` or `file` declares a type that core already has. Installing such a module is meant to fail with "type is already defined by module", but for modules with self calls (always on for Go and Dang) the check only logged and continued. The module's object type was then never installed, as the schema kept core's type of that name, while its constructor still replaced the core root field. Every call through the main object failed (in Dang with "convert return value", in Go with "cannot instantiate dagql.Class[*core.Directory]"), and a Dang module that called the core root field it had replaced hung. Self calls add the module to its own dependencies, so its own types are found there. Accept only that match, and refuse a clash with core or with another dependency again, as for modules without self calls. This relies on a module's self dependency being the module itself. A module cloned into a new result, as implementation scoping does, kept the self entry of the module it was cloned from beside its own. Mark the self entry and replace it on attachment instead. Signed-off-by: Erik Sipsma <erik@sipsma.dev>
sipsma
force-pushed
the
sipsma/refuse-core-type-module-names
branch
from
November 11, 2026 01:29
121e8a1 to
3d876cd
Compare
A version 2 manifest lists its dependencies as its entrypoint's own, and the entrypoint runs with a schema of the core and those dependencies. The module itself was added to that schema too: when its dependencies were attached, and again when the engine served the entrypoint's client. A module with the same name as one of its entrypoint's dependencies then put two modules of one name into one schema. A Go module named go, whose generated entrypoint builds it with dagger/go under the name go, is one. The last root field of a name wins and the first type of a name wins, so the entrypoint's go resolved to the module's own constructor, while the Go types stayed dagger/go's, and the module failed to build. An entrypoint's schema is now the core and its own dependencies, never the module, and the engine serves its client exactly that schema. That is also the schema SDKs check a generated entrypoint against. The module's process keeps its self-calls through the module's own schema, as before. An entrypoint with no dependencies of its own runs with the core alone, so the rule holds whether or not it has any. Signed-off-by: Erik Sipsma <erik@sipsma.dev>
A process an exec starts runs as the current client's own copy of its module, found among the modules the client installed: before, only those it served. A module entrypoint's client now serves only the entrypoint's schema, which never holds the module, so the module wasn't among them. The process fell back to the implementation-scoped copy, which keeps the source of whichever client loaded it first, and served a client module from another checkout's tree, or from a path that doesn't exist on this host. The client's own module is now among the candidates, beside the modules it serves. For any other module client it is already one of those. Signed-off-by: Erik Sipsma <erik@sipsma.dev>
sipsma
force-pushed
the
sipsma/refuse-core-type-module-names
branch
from
November 11, 2026 01:29
3d876cd to
59be068
Compare
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.
This PR settles how a module appears in its own dependencies, in three commits.
1. Refuse a module whose main object is named after a core type
A module's main object takes the module's name and isn't namespaced, so a module named
directory,containerorfiledeclares a type that core already has. Such a module should be refused when it loads, withtype "Directory" is already defined by module "daggercore", and modules of SDKs without self calls (TypeScript, Python, Java, PHP) are. But for modules with self calls, which Go and Dang modules always have, the check only logged the clash and carried on (since #10584). The module's object type was then never installed, because the schema kept core's type of that name, while its constructor still replaced the core root field:convert return value: expected string, got map[string]interface {}, in Go withcannot instantiate dagql.Class[*core.Directory].directorycallingDagger.directory) hung on its first call.Self calls add a module to its own dependencies, so its own types are found there. The check now accepts only that match and refuses any other clash, as for modules without self calls. This reverses the Go behaviour that #13381 described as "no longer fails the load".
That relies on a module's self dependency being the module itself. A module cloned into a new result (implementation scoping, SDK operation scoping) kept the self entry of the module it was cloned from beside its own, and would then have refused itself. The self entry is now marked, and attaching a module replaces it instead of appending a second one.
2. An entrypoint's schema never holds the module it builds
A version 2 manifest lists its dependencies as its entrypoint's own, and the entrypoint runs with a schema of the core and those dependencies. The module itself was added to that schema too. A module with the same name as one of its entrypoint's dependencies, such as a Go module named
go, whose generated entrypoint builds it with dagger/go under the namego, then put two modules of one name into one schema: the entrypoint'sgoresolved to the module's own constructor, and the module failed to build. An entrypoint's schema is now the core and its own dependencies, never the module, and an entrypoint with no dependencies of its own runs with the core alone. The module's process keeps its self calls through the module's own schema.3. A process an entrypoint starts runs as the client's own module
With the module no longer in its entrypoint's schema, a process the entrypoint starts couldn't find the client's module among the ones it serves, and fell back to a copy that keeps the source of whichever client loaded it first. The client's own module is now among the candidates.
What users see
go) builds and runs.Tests
TestDang/TestMainObjectNamedAfterCoreType: a modulecontainer(failed withconvert return value) and a moduledirectorythat callsDagger.directory(hung; the test bounds it at 5 minutes).TestGo/TestMainObjectNamedAfterCoreType: a moduledirectorythat callsdag.Directory()(failed withcannot instantiate).TestModuleCloneRebindsSelfDependency: a self-calling module cloned into a new result has only itself as its self dependency, and its schema dispatches to it.TestModule/TestDangModuleEntrypointDependencySharesModuleName: a module named like its entrypoint's dependency; the entrypoint's call reaches the dependency.Each fails without its fix. Without the third commit, four existing
TestModuleLoading/TestServeModuleCallerTreecases fail.