Test spec: config file paths
What to do with this file
Section titled “What to do with this file”You are a coding agent. Your job is to execute this test spec by running commands in a shell, step by step, from Phase 0 through Phase 5. For each step:
- Run the command shown in the code block.
- Read the output.
- Check every Assert listed for that step.
- Record the result: PASS or FAIL with the actual output.
At the end, produce a summary table showing which phases passed and which failed.
If a prerequisite is not met (binary not found, fixture script missing), stop and report that instead of continuing.
What this test is checking
Section titled “What this test is checking”The Promptless CLI resolves the config file path in three ways, in this order:
$PROMPTLESS_CLI_DEVELOPER_CONFIG_FILE— escape hatch for tests and for running multiple identities side by side.$XDG_CONFIG_HOME/promptless/env— XDG-compliant location.~/.config/promptless/env— default when neither override is set.
Each phase here is an independent one-shot: set env vars, run login,
verify where the file landed, clean up. There’s no carried state — every
phase starts from a clean shell.
This spec also pins down the filesystem permissions that must be applied
on creation: file mode 0600 and parent directory mode 0700. Anything
looser leaks the bearer token to other local users.
For the linear login → whoami → logout flow, see Test spec: authentication happy path. For the fixture contract, see the “Fixture” section of that document.
Prerequisites
Section titled “Prerequisites”The promptless binary and the promptless-test-fixture helper must be on
$PATH. As a smoke test:
$ promptless --help | head -1Shared helper (define once)
Section titled “Shared helper (define once)”The “drive login” step is repeated in several phases. Define it as a shell function so each phase reads cleanly.
$ drive_login() { local stderr_file=$1 promptless login --no-browser > "$stderr_file" 2>&1 & local pid=$! local url="" for i in 1 2 3 4 5; do url=$(grep -o "$URL/cli/auth?[^ ]*" "$stderr_file" 2>/dev/null | head -1) [ -n "$url" ] && break sleep 0.2 done [ -z "$url" ] && { echo "no auth URL captured" >&2; return 1; } curl -sL "$url" -o /dev/null wait $pid }Phase 0: Start the fake server, clean the environment
Section titled “Phase 0: Start the fake server, clean the environment”$ unset PROMPTLESS_CLI_API_SECRET$ unset PROMPTLESS_CLI_DEVELOPER_CONFIG_FILE$ unset XDG_CONFIG_HOME$ eval "$(promptless-test-fixture start-fake-server)"$ export PROMPTLESS_APP_BASE_URL=$URL$ export PROMPTLESS_API_BASE_URL=$URLAssert: $URL is non-empty and starts with http://127.0.0.1:.
Phase 1: PROMPTLESS_CLI_DEVELOPER_CONFIG_FILE wins over everything
Section titled “Phase 1: PROMPTLESS_CLI_DEVELOPER_CONFIG_FILE wins over everything”The developer override is highest priority. When set, neither
$XDG_CONFIG_HOME nor the default ~/.config path should be touched.
To prove the latter, set $XDG_CONFIG_HOME to a known-fresh location and
verify the developer override path is used instead.
$ DEV_CFG=$(mktemp -u /tmp/promptless-dev-XXXXXX.env)$ XDG_FRESH=$(mktemp -d /tmp/promptless-xdg-fresh-XXXXXX)$ export PROMPTLESS_CLI_DEVELOPER_CONFIG_FILE=$DEV_CFG$ export XDG_CONFIG_HOME=$XDG_FRESH$ drive_login /tmp/promptless-phase1.stderrAssert: Login exits 0.
$ test -f $DEV_CFG && echo "dev: found" || echo "dev: missing"$ ls -1 $XDG_FRESHAssert:
$DEV_CFGexists — the file landed at the developer override path.$XDG_FRESHis empty (or contains nopromptless/subdir) — the XDG path was not touched while the override was set.
Clean up before the next phase.
$ rm -f $DEV_CFG /tmp/promptless-phase1.stderr$ rm -rf $XDG_FRESH$ unset PROMPTLESS_CLI_DEVELOPER_CONFIG_FILEPhase 2: $XDG_CONFIG_HOME is used when developer override is absent
Section titled “Phase 2: $XDG_CONFIG_HOME is used when developer override is absent”With the developer override unset, the XDG path takes priority over the
default. The CLI must also create the promptless subdirectory if it
doesn’t exist.
$ export XDG_CONFIG_HOME=$(mktemp -d /tmp/promptless-xdg-XXXXXX)$ ls -1 $XDG_CONFIG_HOMEAssert: Directory is empty.
$ drive_login /tmp/promptless-phase2.stderr$ ls -1 $XDG_CONFIG_HOME/promptless/Assert:
- Login exits 0.
$XDG_CONFIG_HOME/promptless/envexists.- The directory contains exactly one entry:
env.
Phase 3: File and directory permissions
Section titled “Phase 3: File and directory permissions”A bearer token in a world-readable file is no better than one in /tmp.
The CLI applies 0600 to the file and 0700 to the parent directory on
every write.
$ stat -f '%Lp' $XDG_CONFIG_HOME/promptless/env 2>/dev/null || stat -c '%a' $XDG_CONFIG_HOME/promptless/env$ stat -f '%Lp' $XDG_CONFIG_HOME/promptless 2>/dev/null || stat -c '%a' $XDG_CONFIG_HOME/promptlessAssert:
- File mode is
600. - Directory mode is
700.
As an extra check, deliberately loosen the file mode and re-run login —
the CLI should re-apply 0600 even if the file already existed.
$ chmod 0644 $XDG_CONFIG_HOME/promptless/env$ drive_login /tmp/promptless-phase3.stderr$ stat -f '%Lp' $XDG_CONFIG_HOME/promptless/env 2>/dev/null || stat -c '%a' $XDG_CONFIG_HOME/promptless/envAssert: Output is 600 — login re-tightened the mode.
Phase 4: Default ~/.config/promptless/env (documented, not exercised)
Section titled “Phase 4: Default ~/.config/promptless/env (documented, not exercised)”The fallback path when neither override is set is ~/.config/promptless/env.
We deliberately do not exercise this path in the automated test, because doing so would write to the real developer’s home directory and risk overwriting their actual cached credentials.
The resolution logic that produces this path is the same as the XDG path
in Phase 2, just with a hardcoded base. The code is
src/lib/config.ts:resolveConfigFilePath:
const base = xdgConfigHome && xdgConfigHome.length > 0 ? xdgConfigHome : join(homedir(), '.config')return join(base, 'promptless', 'env')Since Phase 2 proved the XDG branch works and creates the promptless/
subdirectory, the default branch is covered by inspection of this code
plus the empty-string fallback test.
Assert: Verify the source code at the path above still uses the same
fallback pattern (xdgConfigHome && xdgConfigHome.length > 0 → otherwise
homedir() + '.config'). If the implementation has changed, this spec is
out of date.
Phase 5: Teardown
Section titled “Phase 5: Teardown”$ promptless-test-fixture stop-fake-server $PID$ rm -rf $XDG_CONFIG_HOME$ rm -f /tmp/promptless-phase2.stderr /tmp/promptless-phase3.stderr$ unset XDG_CONFIG_HOME PROMPTLESS_APP_BASE_URL PROMPTLESS_API_BASE_URLAssert:
- Fake server stopped.
- No
/tmp/promptless-*files remain.
Summary of Key Assertions
Section titled “Summary of Key Assertions”| Phase | What is tested | Key assertion |
|---|---|---|
| 0 | Setup | Fake server up; env clean |
| 1 | Developer override wins | File at $PROMPTLESS_CLI_DEVELOPER_CONFIG_FILE; XDG path untouched |
| 2 | XDG fallback | $XDG_CONFIG_HOME/promptless/env created |
| 3 | Permissions | File 0600, dir 0700; re-applied on every login |
| 4 | Default ~/.config/ | Documented via source-code inspection (not exercised) |
| 5 | Teardown | Server stopped, temp dirs removed |