dagger/dagger · error · IntrospectionError
type ${JSON.stringify(typeDef)} has already been resolved, i
Error message
type ${JSON.stringify(typeDef)} has already been resolved, it should not be overwritten ; reference: ${JSON.stringify(reference)} What it means
During TypeDef reference propagation, resolveTypeDef reached a TypeDef that already had its inner `typeDef` filled in, so resolving it again with the given reference would overwrite an already-resolved value. This is an internal invariant violation: it indicates a reference graph where the same TypeDef is being resolved twice or references were applied in the wrong order.
Source
Thrown at sdk/typescript/src/module/introspector/typescript_module/typedef_utils.ts:38
return true
}
export function resolveTypeDef(
typeDef: TypeDef<TypeDefKind> | undefined,
reference: TypeDef<TypeDefKind>,
): TypeDef<TypeDefKind> {
if (typeDef === undefined) {
return reference
}
if (typeDef.kind === TypeDefKind.ListKind) {
const listTypeDef = typeDef as TypeDef<TypeDefKind.ListKind>
listTypeDef.typeDef = resolveTypeDef(listTypeDef.typeDef, reference)
return listTypeDef
}
throw new IntrospectionError(
`type ${JSON.stringify(typeDef)} has already been resolved, it should not be overwritten ; reference: ${JSON.stringify(reference)}`,
)
}
View on GitHub (pinned to 82ba2681db)
Solutions
- Remove self-referential/cyclic type definitions between your @object classes; Dagger does not support recursive types.
- Ensure each @object class field maps to a distinct TypeDef instance; avoid reusing one shared object across multiple fields.
- Reproduce with a minimal module and, if it looks like an SDK bug, report it to dagger with the module source.
- Upgrade the TypeScript SDK to the latest version, as this may be a fixed introspector regression.
Example fix
// before
class Node { child?: Node } // recursive
// after
class Child { name!: string }
class Node { child?: Child } // no self-reference Defensive patterns
Strategy: validation
Validate before calling
// reject recursive object graphs before registering
function assertNoRecursion(root: Function, seen = new Set<Function>()): void {
if (seen.has(root)) throw new Error(`recursive type: ${root.name}`)
seen.add(root)
// walk fields' types and recurse for @object classes
} Try / catch
try {
await dag.module().throwError()
} catch (e) {
if (String(e).includes("has already been resolved")) {
// indicates cyclic/self-referential type; refactor the object graph
}
throw e
} Prevention
- Do not create self-referential or cyclic @object class types
- Never reuse a single TypeDef-like shared object across multiple fields
- Keep object graphs acyclic: parent -> child -> leaf
When it happens
Trigger: A TypeDef structure is passed through propagateReferences/resolveTypeDef twice (e.g. the same object instance appears in two fields, or a cyclic/self-referential object causes a second traversal), so the leaf TypeDef already has a resolved inner type when resolution recurses into it.
Common situations: Modules with recursive object types (an object referencing itself), duplicated registration of the same class under two fields, or SDK-internal bugs when mutating shared TypeDef instances.
Related errors
- add Query function %q to filtered typedef: %w
- wrap filtered Query typedef: %w
- typedef %q missing canonical name
- normalize typedef optional=false: %w
- failed to convert return type: %w
AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05).
Data as JSON: /api/errors/7fb484053d75042f.
Report an issue: GitHub.