hashicorp/terraform · error

can't use local directory

Error message

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

What it means

Thrown by ParseModuleSourceRegistry when the raw source matches the local-path check (isModuleSourceLocal). This variant is explicitly registry-only and rejects anything that looks like a filesystem path so it can give a clearer error than the generic registry parser would. The %q is the raw input.

Solutions

  1. Replace the local path with a registry address: '<NAMESPACE>/<TYPE>/<PROVIDER>'.
  2. If local is intended, call ParseModuleSource (the general parser) instead of ParseModuleSourceRegistry.
  3. Check the calling context (CLI flag, config field) to confirm a registry address is genuinely required.

Example fix

# before (registry context, local path)
source = "./modules/vpc"

# after (registry address)
source = "terraform-aws-modules/vpc/aws"
Defensive patterns

Strategy: type-guard

Validate before calling

// If the source might be local, do not route through ParseModuleSourceRegistry.
// if isModuleSourceLocal(raw) {
//     return ParseModuleSource(raw) // general parser instead
// }

Type guard

// isLocalModuleSource narrows before choosing the parser.
// func isLocalModuleSource(raw string) bool {
//     return strings.HasPrefix(raw, "./") || strings.HasPrefix(raw, "../") || filepath.IsAbs(raw)
// }

Prevention

When it happens

Trigger: A caller that knows it expects a registry address (e.g. the module installation pipeline in registry mode) is handed a './local' or '../other' style path and refuses it up front.

Common situations: Configuration or flag meant to reference a registry module is given a local path by mistake; a wrapper tool always routes sources through the registry parser regardless of form; migrating from local to registry modules and forgetting to update one source.

Related errors


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

Appendix: 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 d32a084675)