router-for-me/CLIProxyAPI · error

failed to create directory: %v

Error message

failed to create directory: %v

What it means

SaveTokenToFile creates the auth file's parent directory with 0700 before writing the persisted Claude credential. This error means os.MkdirAll failed, so the token was never written and the freshly obtained credential exists only in memory. The underlying %v tells you whether it is permissions, a path conflict, or an invalid path.

Source

Thrown at internal/auth/claude/token.go:79

}

// SaveTokenToFile serializes the Claude token storage to a JSON file.
// This method creates the necessary directory structure and writes the token
// data in JSON format to the specified file path for persistent storage.
// It merges any injected metadata into the top-level JSON object.
//
// Parameters:
//   - authFilePath: The full path where the token file should be saved
//
// Returns:
//   - error: An error if the operation fails, nil otherwise
func (ts *ClaudeTokenStorage) SaveTokenToFile(authFilePath string) error {
	misc.LogSavingCredentials(authFilePath)
	ts.Type = "claude"

	// Create directory structure if it doesn't exist
	if err := os.MkdirAll(filepath.Dir(authFilePath), 0700); err != nil {
		return fmt.Errorf("failed to create directory: %v", err)
	}

	// Merge metadata using helper
	data, errMerge := misc.MergeMetadata(ts, ts.Metadata)
	if errMerge != nil {
		return fmt.Errorf("failed to merge metadata: %w", errMerge)
	}

	// Create the token file
	f, err := os.Create(authFilePath)
	if err != nil {
		return fmt.Errorf("failed to create token file: %w", err)
	}
	defer func() {
		if errClose := f.Close(); errClose != nil {
			log.Errorf("claude token storage: close token file error: %v", errClose)
		}
	}()

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Check the wrapped OS error text: 'permission denied' → fix ownership/permissions of the parent directory (chown/chmod so the runtime user can write).
  2. Verify auth-dir in config.yaml is a directory path that can be created (no file in its place) and is on a writable volume.
  3. In containers, mount a writable volume at the configured auth-dir.
  4. Relax SELinux/AppArmor policies if they are the denier (check audit logs).

Example fix

# before
auth-dir: /var/lib/cliproxy/auths   # owned by root, server runs as app user

# after
sudo chown -R appuser /var/lib/cliproxy
auth-dir: /var/lib/cliproxy/auths
Defensive patterns

Strategy: validation

Validate before calling

dir := filepath.Dir(authFilePath)
if info, err := os.Stat(dir); err == nil && !info.IsDir() {
    return fmt.Errorf("auth path %s is a file, not a directory", dir)
}
if err := os.WriteFile(filepath.Join(dir, ".probe"), nil, 0600); err != nil {
    return fmt.Errorf("auth dir not writable by this user: %w", err)
}

Try / catch

if err := storage.SaveTokenToFile(path); err != nil && strings.Contains(err.Error(), "failed to create directory") {
    // fix ownership/permissions of auth-dir, then re-save the in-memory credential
}

Prevention

When it happens

Trigger: auth-dir (default auths/) points to a location the process cannot create or write: permission denied, a path component that is a regular file, read-only filesystem, or an unset/relative path resolving somewhere unexpected.

Common situations: Running the binary as a different user than the one that owns the auths/ directory; auth-dir misconfigured in config.yaml to a file path or a root-owned location; container images with a read-only layer where auth-dir was not mounted; SELinux/AppArmor denials on the config directory.

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/3085c010d153badd. Report an issue: GitHub.