Skip to content

feat(config): add global config fallback with documented precedence#60

Open
dark-sorceror wants to merge 7 commits into
mainfrom
feat/global-config-fallback
Open

feat(config): add global config fallback with documented precedence#60
dark-sorceror wants to merge 7 commits into
mainfrom
feat/global-config-fallback

Conversation

@dark-sorceror

@dark-sorceror dark-sorceror commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #53

Credentials were project-local only: traceroot login writes ./.traceroot/config.json relative to the current working directory, and discovery read exactly that path — no fallback. Running any command from a different directory (even a subdirectory of the same repo) silently "logged you out," reporting No API key found with no hint that the user was, in fact, logged in one directory over.

This adds a global (per-user) config fallback at ~/.config/traceroot/config.json (or $XDG_CONFIG_HOME/traceroot/config.json), consulted only when no project-local config resolves a value. login still only ever writes the project-local file — the global file is a manual, read-only fallback for credentials you want available everywhere.

How it works

Resolve auth (per field, independently)
  → --api-key / --host flags
    → --env-file <path>
      → TRACEROOT_API_KEY / TRACEROOT_HOST_URL (env)
        → ./.traceroot/config.json (project config)
          → ~/.config/traceroot/config.json (global config, NEW)
            → auto-discovered ./.env

status and doctor now report both which source won and the concrete file path it resolved from, so Config source / config_source in JSON output distinguishes "config" (project) from "global-config".

What's included

Config resolution

  • src/config/manager.ts — new globalConfigPath() / readGlobalConfig(), sharing the same parse/validate logic as the project-local configPath() / readConfig()
  • src/config/resolve.ts — new "global-config" AuthSource; resolveAuth takes an optional readGlobalConfig, consulted after project config and before the auto-discovered .env
  • src/context.ts — wires the real readGlobalConfig into buildContext by default, injectable for tests

Status / doctor / login

  • src/commands/status.tsdescribeSource and JSON config_path resolve to the global config path when that's the winning source
  • src/commands/doctor.ts / src/doctor/checks.tsapi_key_resolved message names the global config path when it won; new informational global_config_file_present check (only emitted when the file exists — its absence is not a warning)
  • src/commands/login.ts — the "already logged in" warning now also triggers when credentials came from the global config (previously only checked apiKeySource === "config"); login itself still only ever writes the project-local file
  • src/commands/shared.ts — the missing-api-key/missing-host CliError now names both checked paths, e.g. (checked ./.traceroot/config.json and ~/.config/traceroot/config.json — config is per-directory, with a global fallback) — paths only, never key material

Docs

  • README.md — documents the full precedence order, the global fallback location, that login never writes it (with a manual cp example), and that status/doctor report the winning source
  • src/config/schema.ts — corrected the stale docstring that claimed the config lives at ~/.traceroot/config.json (a path the CLI never read or wrote)

Test isolation

  • tests/helpers/runCli.ts (74a19e4) — the shared spawn helper now scrubs TRACEROOT_CONFIG_PATH, XDG_CONFIG_HOME, TRACEROOT_API_KEY, and TRACEROOT_HOST_URL for every spawned CLI test, mirroring the existing isolation in tests/output.contract.test.ts. Without this, a developer's real ~/.config/traceroot/config.json or exported TRACEROOT_* vars would resolve credentials and turn hermetic spawn tests into live network calls.

Test plan

  • Full suite: 615/615 vitest tests passing
  • Typecheck, lint, and format all clean
  • Decoy-XDG isolation: suite stays green even with a decoy global config file present at the fake XDG_CONFIG_HOME, confirming spawned tests can't accidentally read it
  • Live e2e: with a real global config at $XDG_CONFIG_HOME/traceroot/config.json and an empty, unrelated cwd — traceroot status resolves credentials from the live workspace, reports Config source: <global config path>, and config_source: "global-config" in --json output
  • traceroot doctor shows "Global config file present" when the global config exists
  • Precedence holds: an env var (TRACEROOT_API_KEY) overrides a present global config
  • Missing-credential error names both checked paths (project and global)
  • Resolution works correctly when invoked from nested subdirectories

Summary by cubic

Adds a per-user global config fallback and shows exactly where credentials come from in status/doctor, fixing the “logged out from subdirectories” issue. Also warns on weak global-config permissions and reads the fallback lazily so higher-precedence sources always win.

  • New Features

    • Global config fallback after project config, before auto .env; login still writes only ./.traceroot/config.json (copy to ~/.config/traceroot/config.json if desired).
    • status/doctor show the winning source and path; JSON reports config_source and config_path (global path when it wins); missing-credential errors name both checked paths.
  • Bug Fixes

    • doctor warns when the global config file is group/world-readable and recommends chmod 600.
    • Global config is read lazily, so unreadable home files never break resolution and flags/env/project config always override.

Written for commit e6fee6a. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 18 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/doctor/checks.ts
Comment thread src/config/resolve.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Credentials are project-local only — running any command from another directory silently "logs you out"

1 participant