twpayne/chezmoi · error

%s: missing external type

Error message

%s: missing external type

What it means

Returned when an external entry in the source state has an empty type field. chezmoi's readExternal dispatch switch falls into the "" case because it cannot know how to interpret the entry without a type (archive, file, git-repo, etc.).

Source

Thrown at internal/chezmoi/sourcestate.go:2425

// readExternal reads an external and returns its SourceStateEntries.
func (s *SourceState) readExternal(
	ctx context.Context,
	externalRelPath RelPath,
	parentSourceRelPath SourceRelPath,
	external *External,
	options *ReadOptions,
) (map[RelPath][]SourceStateEntry, error) {
	switch external.Type {
	case ExternalTypeArchive:
		return s.readExternalArchive(ctx, externalRelPath, parentSourceRelPath, external, options)
	case ExternalTypeArchiveFile:
		return s.readExternalArchiveFile(ctx, externalRelPath, parentSourceRelPath, external, options)
	case ExternalTypeFile:
		return s.readExternalFile(ctx, externalRelPath, parentSourceRelPath, external, options)
	case ExternalTypeGitRepo:
		return nil, nil
	case "":
		return nil, fmt.Errorf("%s: missing external type", externalRelPath)
	default:
		return nil, fmt.Errorf("%s: unknown external type: %s", externalRelPath, external.Type)
	}
}

// readExternalArchive reads an external archive and returns its
// SourceStateEntries.
func (s *SourceState) readExternalArchive(
	ctx context.Context,
	externalRelPath RelPath,
	parentSourceRelPath SourceRelPath,
	external *External,
	options *ReadOptions,
) (map[RelPath][]SourceStateEntry, error) {
	data, urlStr, format, err := s.readExternalArchiveData(ctx, externalRelPath, external, options)
	if err != nil {
		return nil, err
	}

View on GitHub (pinned to f901167e46)

Solutions

  1. Add the missing type field to the external entry (archive, file, or git-repo)
  2. Use the executable entry format (type = "archive-file" with executable) if that was intended
  3. Validate .chezmoiexternal with chezmoi doctor or a dry-run apply

Example fix

// before
[.oh-my-zsh]
    url = "https://github.com/ohmyzsh/ohmyzsh/archive/master.tar.gz"
// after
[.oh-my-zsh]
    type = "archive"
    url = "https://github.com/ohmyzsh/ohmyzsh/archive/master.tar.gz"
Defensive patterns

Strategy: validation

Validate before calling

// pre-validate external entries
for name, ext := range externals {
    if ext.Type == "" { return fmt.Errorf("external %q is missing type", name) }
}

Type guard

func hasType(e External) bool { return e.Type != "" }

Prevention

When it happens

Trigger: An entry in .chezmoiexternal defines a url (or path) but omits the 'type' key, and the type cannot be inferred (e.g. no archive format guess possible for the given path/URL).

Common situations: Hand-written external config missing the type line; migration from an older chezmoi format; templating that dropped the type field.

Related errors


AI-assisted analysis of twpayne/chezmoi@f901167e46 (2026-09-01). Data as JSON: /api/errors/5da1117f4d8e4596. Report an issue: GitHub.