hasura/graphql-engine · error · errors.Error

cannot create global config directory: %w

Error message

cannot create global config directory: %w

What it means

os.MkdirAll(ec.GlobalConfigDir, os.ModePerm) failed while creating the global config directory. This wraps mkdir syscall errors: permission denied on a parent, a path component that exists as a file (ENOTDIR), read-only filesystem, or I/O errors. It fires for both the default (~/.<name>) and any custom GlobalConfigDir.

Source

Thrown at cli/global_config.go:132

	var op errors.Op = "cli.ExecutionContext.setupGlobalConfig"
	// check if the directory name is set, else default
	if len(ec.GlobalConfigDir) == 0 {
		ec.Logger.Debug("global config directory is not pre-set, defaulting")

		home, err := os.UserHomeDir()
		if err != nil {
			return errors.E(op, fmt.Errorf("cannot get home directory: %w", err))
		}

		globalConfigDir := filepath.Join(home, GlobalConfigDirName)
		ec.GlobalConfigDir = globalConfigDir
		ec.Logger.Debugf("global config directory set as '%s'", ec.GlobalConfigDir)
	}

	// create the config directory
	err := os.MkdirAll(ec.GlobalConfigDir, os.ModePerm)
	if err != nil {
		return errors.E(op, fmt.Errorf("cannot create global config directory: %w", err))
	}

	// check if the filename is set, else default
	if len(ec.GlobalConfigFile) == 0 {
		ec.GlobalConfigFile = filepath.Join(ec.GlobalConfigDir, GlobalConfigFileName)
		ec.Logger.Debugf("global config file set as '%s'", ec.GlobalConfigFile)
	}

	// check if the global config file exist
	_, err = os.Stat(ec.GlobalConfigFile)
	if stderrors.Is(err, fs.ErrNotExist) {
		// file does not exist, teat as first run and create it
		ec.Logger.Debug(
			"global config file does not exist, this could be the first run, creating it...",
		)

		// create an empty config object
		gc := &rawGlobalConfig{}

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Manually create the directory with correct ownership: mkdir -p + chown
  2. Check that no path component is a regular file (ls -la each level) and remove/rename it
  3. Point GlobalConfigDir at a writable location if the filesystem is read-only
  4. Run with sufficient privileges or fix SELinux/AppArmor labels if enforced

Example fix

// before
ec.GlobalConfigDir = "/etc/mycli"
// after
ec.GlobalConfigDir = filepath.Join(os.Getenv("HOME"), ".mycli")
Defensive patterns

Strategy: validation

Validate before calling

if err := os.MkdirAll(ec.GlobalConfigDir, 0o755); err != nil {
    return fmt.Errorf("cannot pre-create config dir %s: %w", ec.GlobalConfigDir, err)
}

Type guard

func dirIsCreatable(p string) bool {
    return os.MkdirAll(p, 0o755) == nil
}

Try / catch

if err := ec.Prepare(ctx); err != nil {
    if strings.Contains(err.Error(), "cannot create global config directory") {
        ec.GlobalConfigDir = filepath.Join(os.TempDir(), GlobalConfigDirName)
        return ec.Prepare(ctx)
    }
    return err
}

Prevention

When it happens

Trigger: GlobalConfigDir points under a root-owned path while running unprivileged, a path component (e.g. ~/.config) is actually a regular file, or the filesystem is read-only (immutable container, squashfs).

Common situations: Running the CLI as non-root against /etc/... paths, leftover files where directories are expected, read-only rootfs in Kubernetes, or SELinux/AppArmor denying mkdir.

Related errors


AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28). Data as JSON: /api/errors/a0e80c530268ef25. Report an issue: GitHub.