hasura/graphql-engine · error · errors.Error

write file: %w

Error message

write file: %w

What it means

The write method of rawGlobalConfig failed to persist the marshaled config bytes to disk via os.WriteFile. This is an I/O error: the target path is unwritable, a parent directory is missing, or permissions deny creation/truncation of the file. It is thrown after a successful marshal, so the data itself is valid; only writing failed.

Source

Thrown at cli/global_config.go:105

	if c.CLIEnvironment == "" {
		c.CLIEnvironment = DefaultEnvironment
	}

	return nil
}

func (c *rawGlobalConfig) write(filename string) error {
	var op errors.Op = "cli.rawGlobalConfig.write"

	b, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		return errors.E(op, fmt.Errorf("marshal file: %w", err))
	}

	err = os.WriteFile(filename, b, 0o644)
	if err != nil {
		return errors.E(op, fmt.Errorf("write file: %w", err))
	}

	return nil
}

// setupGlobConfig ensures that global config directory and file exists and
// reads it into the GlobalConfig object.
func (ec *ExecutionContext) setupGlobalConfig() error {
	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))
		}

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Check permissions/ownership of the config file and its parent directory (ls -l, chown/chmod) and make the dir writable
  2. Verify HOME (or the configured GlobalConfigFile path) is writable: touch the path manually to reproduce
  3. If on a read-only filesystem, point GlobalConfigDir/GlobalConfigFile to a writable mount or tmpfs
  4. Ensure the parent directory exists before writing (MkdirAll runs earlier for the default dir, but a custom file path may bypass it)

Example fix

// before
ec.GlobalConfigFile = "/etc/app/config.json"
// after
cfgDir := filepath.Join(os.TempDir(), "app")
_ = os.MkdirAll(cfgDir, 0o755)
ec.GlobalConfigFile = filepath.Join(cfgDir, "config.json")
Defensive patterns

Strategy: validation

Validate before calling

if dir := filepath.Dir(ec.GlobalConfigFile); dir != "" {
    if info, err := os.Stat(dir); err != nil || !info.IsDir() {
        _ = os.MkdirAll(dir, 0o755)
    }
}
if f, err := os.OpenFile(ec.GlobalConfigFile, os.O_WRONLY|os.O_CREATE, 0o644); err != nil {
    return fmt.Errorf("config path not writable: %w", err)
}
_ = f.Close()

Type guard

func isWritable(path string) bool {
    f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0o644)
    if err != nil {
        return false
    }
    _ = f.Close()
    return true
}

Try / catch

if err := ec.Prepare(ctx); err != nil {
    if strings.Contains(err.Error(), "write file:") {
        log.Printf("config path %s not writable; redirecting to temp dir", ec.GlobalConfigFile)
        ec.GlobalConfigFile = filepath.Join(os.TempDir(), GlobalConfigFileName)
        return ec.Prepare(ctx)
    }
    return err
}

Prevention

When it happens

Trigger: Calling setupGlobalConfig (via Prepare) when ec.GlobalConfigFile points to a path that does not exist, is on a read-only filesystem, or is owned by another user (e.g. running as non-root against a root-owned config file, or an unwritable $HOME).

Common situations: Running the CLI in a container with a read-only root filesystem, HOME set to a non-writable dir, a leftover config file created by root (permission denied on truncate), or a custom GlobalConfigFile pointing into a directory that was never created.

Related errors


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