hashicorp/terraform · error

can't use local directory %q as a module registry address

Error message

can't use local directory %q as a module registry address

What it means

Raised by ParseModuleSourceRegistry (internal/getmodules/moduleaddrs/source_parsing.go:156). This strict parser accepts ONLY registry addresses, so if the source begins with a local-path prefix (./, ../, .\, ..\) it is rejected outright with this message. The caller chose the registry-only parser but supplied a local relative path.

Source

Thrown at internal/getmodules/moduleaddrs/source_parsing.go:156

			return true
		}
	}
	return false
}

// ParseModuleSourceRegistry is a variant of ParseModuleSource which only
// accepts module registry addresses, and will reject any other address type.
//
// Use this instead of ParseModuleSource if you know from some other surrounding
// context that an address is intended to be a registry address rather than
// some other address type, which will then allow for better error reporting
// due to the additional information about user intent.
func ParseModuleSourceRegistry(raw string) (addrs.ModuleSource, error) {
	// Before we delegate to the "real" function we'll just make sure this
	// doesn't look like a local source address, so we can return a better
	// error message for that situation.
	if isModuleSourceLocal(raw) {
		return addrs.ModuleSourceRegistry{}, fmt.Errorf("can't use local directory %q as a module registry address", raw)
	}

	src, err := tfaddr.ParseModuleSource(raw)
	if err != nil {
		return nil, err
	}
	return addrs.ModuleSourceRegistry{
		Package: src.Package,
		Subdir:  src.Subdir,
	}, nil
}

func parseModuleSourceRemote(raw string) (addrs.ModuleSourceRemote, error) {
	var subDir string
	raw, subDir = SplitPackageSubdir(raw)
	if strings.HasPrefix(subDir, "../") {
		return addrs.ModuleSourceRemote{}, fmt.Errorf("subdirectory path %q leads outside of the module package", subDir)
	}

View on GitHub (pinned to c9def3e214)

Solutions

  1. If local paths should be allowed, call ParseModuleSource (the general parser) instead of ParseModuleSourceRegistry.
  2. If only registry addresses are valid for your context, reject or re-route local paths upstream and surface a clearer message to the user.
  3. Ensure the input is a genuine registry address (namespace/name/system or host/namespace/name/system).

Example fix

// before
addr, err := moduleaddrs.ParseModuleSourceRegistry("./modules/foo")
// after
addr, err := moduleaddrs.ParseModuleSource("./modules/foo")
Defensive patterns

Strategy: validation

Validate before calling

// Dispatch to the right parser based on whether the source is local.
func parseSource(raw string) (addrs.ModuleSource, error) {
	for _, p := range []string{"./", "../", ".\\", "..\\"} {
		if strings.HasPrefix(raw, p) {
			// Local path: do NOT use the registry-only parser.
			return moduleaddrs.ParseModuleSource(raw)
		}
	}
	return moduleaddrs.ParseModuleSourceRegistry(raw)
}

Try / catch

if _, err := moduleaddrs.ParseModuleSourceRegistry(raw); err != nil {
    if strings.HasPrefix(raw, "./") || strings.HasPrefix(raw, "../") {
        // Wrong parser for a local path; retry with the general parser.
        return moduleaddrs.ParseModuleSource(raw)
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling ParseModuleSourceRegistry("./modules/foo") or ParseModuleSourceRegistry("../shared/network"). Any string starting with a recognized local prefix trips this guard before registry parsing is attempted.

Common situations: A tool or code path that intentionally restricts inputs to registry addresses receiving a local path; user confusion between the general ParseModuleSource and the registry-only variant; misrouted input in a wrapper CLI.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/b6177ec3779e4056. Report an issue: GitHub.