Configuration File Resolution Order for Ponytail: Environment Variables, JSON Configs, and Fallbacks

Ponytail determines its default runtime mode through a strict three-tier hierarchy: first checking the PONYTAIL_DEFAULT_MODE environment variable, then the defaultMode property in a platform-specific config.json file, and finally defaulting to the built-in constant 'full' when no valid external configuration exists.

The DietrichGebert/ponytail repository implements this resolution logic in hooks/ponytail-config.js to ensure consistent behavior across Unix, macOS, and Windows environments. Understanding this precedence order allows developers to effectively manage runtime modes (off, lite, full, or ultra) across development, CI, and production deployments.

The Three-Level Configuration Hierarchy

The getDefaultMode() function in hooks/ponytail-config.js evaluates configuration sources sequentially, returning immediately upon finding the first valid runtime mode. Invalid values at any level trigger a fallthrough to the next source rather than causing errors.

1. Environment Variable: PONYTAIL_DEFAULT_MODE

The resolver inspects process.env.PONYTAIL_DEFAULT_MODE before accessing the filesystem. If the variable exists and contains a valid mode string—off, lite, full, or ultra—the function returns the lowercase value immediately. This top-level priority enables temporary, session-specific overrides without modifying persistent configuration files.


# Override for current shell session

export PONYTAIL_DEFAULT_MODE=lite

2. Platform-Specific Configuration Files

When the environment variable is unset or invalid, Ponytail searches for config.json in platform-specific configuration directories. The resolution follows XDG Base Directory specifications on Unix systems and Windows conventions:

  • $XDG_CONFIG_HOME/ponytail/config.json (checked first if XDG_CONFIG_HOME is defined)
  • ~/.config/ponytail/config.json on macOS and Linux (fallback)
  • %APPDATA%\ponytail\config.json on Windows

The file must contain a top-level defaultMode property. As implemented in hooks/ponytail-config.js (lines 76–99), the parser strips UTF-8 BOM characters before calling JSON.parse() to ensure compatibility with editors that insert byte-order marks.

{
  "defaultMode": "ultra",
  "quietStartup": true,
  "hideStatus": false
}

3. Built-in Default Mode Constant

If neither the environment variable nor a valid configuration file exists, the function returns DEFAULT_MODE, a constant hard-coded to 'full'. This fallback ensures Ponytail remains operational in zero-configuration environments, providing sensible defaults for first-time users.

Implementation in hooks/ponytail-config.js

The resolution logic resides in the getDefaultMode() function, which hooks/ponytail-mode-tracker.js and pi-extension/index.js consume during initialization. The implementation uses explicit validation against the RUNTIME_MODES array to prevent invalid state propagation:

// 1. Environment variable (highest priority)
const envMode = process.env.PONYTAIL_DEFAULT_MODE;
if (envMode && RUNTIME_MODES.includes(envMode.toLowerCase())) {
  return envMode.toLowerCase();
}

// 2. Config file
try {
  const configPath = getConfigPath();
  const config = JSON.parse(
    fs.readFileSync(configPath, 'utf8').replace(/^\uFEFF/, '')
  );
  if (config.defaultMode && RUNTIME_MODES.includes(config.defaultMode.toLowerCase())) {
    return config.defaultMode.toLowerCase();
  }
} catch (e) {
  // Config file missing or malformed – fall through
}

// 3. Default
return DEFAULT_MODE;

This structure ensures that malformed JSON or invalid mode strings in config.json do not crash the application; instead, the resolver silently proceeds to the built-in fallback.

Practical Usage Examples

Overriding Defaults in Shell Sessions

For CI pipelines or temporary testing, set the mode without touching the filesystem:


# Bash/Zsh

PONYTAIL_DEFAULT_MODE=off ponytail process ./files

# Windows PowerShell

$env:PONYTAIL_DEFAULT_MODE="lite"; ponytail process ./files

Creating Persistent Configuration Files

Establish a default mode for all future invocations by creating the appropriate directory structure and JSON file:


# Linux/macOS

mkdir -p ~/.config/ponytail
echo '{"defaultMode": "full"}' > ~/.config/ponytail/config.json

# Windows PowerShell

New-Item -ItemType Directory -Force -Path "$env:APPDATA\ponytail"
Set-Content -Path "$env:APPDATA\ponytail\config.json" -Value '{"defaultMode": "full"}'

Reading and Writing Defaults Programmatically

The pi-extension/index.js file exposes utilities for external callers and plugins. Read the resolved effective default:

const { getDefaultMode } = require('./hooks/ponytail-config');

console.log('Resolved mode:', getDefaultMode()); // "full", "lite", "ultra", or "off"

Persist a new default to config.json using writeDefaultMode():

const { writeDefaultMode } = require('./hooks/ponytail-config');

// Updates or creates config.json with "defaultMode": "lite"
writeDefaultMode('lite');

Summary

  • Environment variables take precedence: PONYTAIL_DEFAULT_MODE overrides file-based configuration when set to a valid mode.
  • XDG compliance on Unix: The resolver checks $XDG_CONFIG_HOME before falling back to ~/.config/, following freedesktop.org standards.
  • Windows uses APPDATA: On Windows systems, configuration resides in %APPDATA%\ponytail\config.json.
  • UTF-8 BOM tolerance: The JSON parser strips byte-order marks to prevent parsing errors from editor-generated files.
  • Graceful degradation: Invalid values at any level trigger fallthrough to the next source, ultimately defaulting to 'full' mode.
  • Programmatic API available: getDefaultMode() and writeDefaultMode() in hooks/ponytail-config.js enable runtime inspection and persistent updates.

Frequently Asked Questions

What happens if the config.json file contains an invalid mode name?

If the defaultMode property in config.json contains a value not included in the RUNTIME_MODES array (such as off, lite, full, or ultra), the resolver ignores it and falls through to the built-in default of 'full'. Similarly, malformed JSON or missing files trigger the same fallback behavior without throwing errors.

Can I override the configuration file location using an environment variable?

No. As implemented in hooks/ponytail-config.js, the getConfigPath() function uses hard-coded platform logic based on $XDG_CONFIG_HOME and %APPDATA%. There is no environment variable to specify an alternative config file path; you must place the file in one of the standard platform-specific locations.

Does changing the configuration file affect already-running Ponytail processes?

No. The configuration resolution occurs once during process initialization, typically when ponytail-mode-tracker.js invokes getDefaultMode(). Modifications to config.json or the PONYTAIL_DEFAULT_MODE environment variable only affect processes started after the change; existing processes retain their initially resolved mode.

How do I programmatically determine which configuration source is active?

Ponytail does not expose the specific source of the resolved default through its public API. However, you can infer the source by checking process.env.PONYTAIL_DEFAULT_MODE before requiring the config module—if the environment variable is set and valid, it is the active source. Otherwise, if getDefaultMode() returns a value other than 'full', it likely originated from config.json.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →