Skip to content

feat(config): add XDG and GOG path overrides#634

Merged
steipete merged 6 commits into
mainfrom
feat/xdg-gog-home-paths
May 22, 2026
Merged

feat(config): add XDG and GOG path overrides#634
steipete merged 6 commits into
mainfrom
feat/xdg-gog-home-paths

Conversation

@steipete

@steipete steipete commented May 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR implements the #621/#622 path model as one change:

  • Add kind-aware config/data/state/cache path resolution with GOG_HOME, per-kind GOG_CONFIG_DIR, GOG_DATA_DIR, GOG_STATE_DIR, GOG_CACHE_DIR, and global --home.
  • Move new auth metadata, file-keyring data, service-account keys, tracking state, Gmail watch state, and backup/cache state to the correct kind-specific roots.
  • Preserve legacy reads for normal migration, but keep explicit GOG_HOME/per-kind overrides isolated so CI, containers, and agents do not silently import host secrets/state.
  • Keep Drive/Gmail download defaults unchanged unless the command already has its own output flag.
  • Document path precedence, XDG behavior, compatibility rules, and container/systemd usage.

Compatibility notes

  • Relative GOG_*_DIR and --home values are rejected; relative XDG values are ignored per XDG expectations.
  • Legacy OAuth credentials, service-account keys, file-keyring directories, tracking config, backup config, and Gmail watch files remain readable when no explicit gog-specific override is active.
  • Existing legacy file-keyring secrets remain authoritative when both old and new data roots exist, so migration does not hide stored client secrets.
  • Explicit gog-specific roots intentionally skip legacy secret/state fallback to preserve sandbox isolation.
  • --home applies before parser/help rendering even when placed after a subcommand.

Proof

  • make ci
  • go test ./internal/config ./internal/secrets ./internal/cmd ./internal/googleapi ./internal/backup ./internal/tracking
  • make lint
  • GOOS=windows GOARCH=amd64 go test -c ./internal/tracking -o /tmp/gogcli-tracking-windows.test.exe
  • GOOS=windows GOARCH=amd64 .tools/golangci-lint run ./internal/tracking
  • GitHub Actions PR CI run 26291047143: test, worker, windows, and darwin-cgo-build all green
  • Live smoke: isolated GOG_HOME writes credentials metadata to GOG_HOME/data, config path to GOG_HOME/config, keeps client_secret out of metadata, and lists the keyring-backed client
  • Live smoke: legacy credentials in the old config dir still list and remove when no explicit data override is set
  • Autoreview: /Users/steipete/Projects/agent-scripts/skills/autoreview/scripts/autoreview --mode branch --base origin/main clean, no accepted/actionable findings

Closes #621.
Closes #622.

Thanks @alexminza for the original proposal and direction.

@clawsweeper

clawsweeper Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge.

Latest ClawSweeper review: 2026-05-22 13:48 UTC / May 22, 2026, 9:48 AM ET.

Workflow note: Future ClawSweeper reviews update this same comment in place.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Summary
The PR adds XDG/GOG config, data, state, and cache path resolution with GOG_HOME, per-kind GOG_*_DIR overrides, a global --home flag, migration fallbacks, docs, and tests.

Reproducibility: yes. for the review findings: source inspection of PR head shows the early --home scanner can consume subcommand values, Gmail watch legacy files become save targets, and download defaults still derive from the new config resolver. The broader item is a feature/configuration change, not a bug report.

PR rating
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster
Patch quality: 🦐 gold shrimp
Summary: Strong proof and useful feature work, but source-reproducible P2 blockers keep the patch below merge-ready quality.

Rank-up moves:

  • Fix the --home pre-scan false positive for subcommand flag values.
  • Fix Gmail watch read-legacy/write-new behavior and keep download defaults independent of config/home overrides.
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Real behavior proof
Sufficient (live_output): The PR body reports focused command output plus live smoke evidence for isolated GOG_HOME credential placement and legacy credential reads/removal.

Risk before merge

  • Existing users with OAuth credentials, file-keyring data, service-account keys, backup config, tracking state, or Gmail watch cursors can be affected by the new roots and fallback rules during upgrade.
  • Explicit GOG_HOME and per-kind overrides intentionally skip legacy secret/state fallback, so authenticated flows may appear unconfigured until operators migrate or point gogcli at the intended root.
  • Current head still has source-reproducible behavior that can reject valid subcommand inputs, keep Gmail watch writes in legacy state, and move default downloads despite documentation saying downloads are unchanged.

Maintainer options:

  1. Repair parser and migration blockers first (recommended)
    Fix the --home pre-scan false positive, Gmail watch read-legacy/write-new behavior, and download default routing before merge.
  2. Accept explicit-root isolation after repair
    After source blockers are fixed, maintainers can intentionally accept the documented behavior that explicit gog-specific roots do not import legacy secrets or state.
  3. Pause for broader upgrade proof
    If migration blast radius remains uncertain, pause until redacted live output covers legacy reads and explicit-root isolation across credentials, keyring, service accounts, Gmail watch, and tracking state.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Fix `preScanHomeArg` so it only treats `--home` as the global flag in valid global-flag positions and does not consume subcommand flag values literally named `--home` or `--home=...`. Fix Gmail watch state handling so legacy account files can be read for upgrade compatibility but subsequent saves write to the new state root. Keep Drive/Gmail default download directories independent of `GOG_HOME`, `--home`, and `GOG_CONFIG_DIR` as documented. Add focused regression tests for each case and preserve explicit override isolation for credentials, keyring data, service-account keys, tracking state, and backup config.

Next step before merge
A focused repair can address the concrete P2 findings, while maintainers still own final compatibility signoff for the path model.

Security
Cleared: No concrete security or supply-chain regression found; the patch is credential-path sensitive but does not add dependencies, CI execution, or broader secret access.

Review findings

  • [P2] Restrict pre-scan to true global flag positions — internal/cmd/root.go:285-292
  • [P2] Write Gmail watch updates to the new state root — internal/cmd/gmail_watch_state.go:42-45
  • [P2] Keep download defaults out of the config override root — internal/config/paths.go:396-424
Review details

Best possible solution:

Land the path model only after the remaining parser and migration blockers are fixed, with focused regression tests and explicit maintainer compatibility signoff for the upgrade behavior.

Do we have a high-confidence way to reproduce the issue?

Yes for the review findings: source inspection of PR head shows the early --home scanner can consume subcommand values, Gmail watch legacy files become save targets, and download defaults still derive from the new config resolver. The broader item is a feature/configuration change, not a bug report.

Is this the best way to solve the issue?

No; the path model is directionally useful, but this branch is not yet the safest way to land it because it still needs focused parser, migration, and default-download repairs.

Label justifications:

  • P2: This is a normal-priority feature/configuration PR with bounded but real upgrade and path-compatibility impact.
  • merge-risk: 🚨 compatibility: The diff changes persisted path resolution, documented defaults, and legacy fallback behavior for existing installations.
  • merge-risk: 🚨 auth-provider: OAuth client metadata, file-keyring data, and service-account key lookup move between config/data roots.
  • merge-risk: 🚨 session-state: Gmail watch cursors and tracking state move to state roots with legacy fallback behavior.
  • rating: 🦐 gold shrimp: Current PR rating is 🦐 gold shrimp because proof is 🦞 diamond lobster, patch quality is 🦐 gold shrimp, and Strong proof and useful feature work, but source-reproducible P2 blockers keep the patch below merge-ready quality.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (live_output): The PR body reports focused command output plus live smoke evidence for isolated GOG_HOME credential placement and legacy credential reads/removal.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body reports focused command output plus live smoke evidence for isolated GOG_HOME credential placement and legacy credential reads/removal.

Full review comments:

  • [P2] Restrict pre-scan to true global flag positions — internal/cmd/root.go:285-292
    preScanHomeArg() scans every token before Kong knows command structure, so a subcommand flag value of --home or --home=... is mistaken for the global home override and can turn a valid command into an early usage error.
    Confidence: 0.89
  • [P2] Write Gmail watch updates to the new state root — internal/cmd/gmail_watch_state.go:42-45
    When a legacy Gmail watch file exists, gmailWatchStatePath() returns that legacy file as the store path, so Save() keeps writing updates into the old config tree instead of reading legacy once and writing future state to the new state root.
    Confidence: 0.86
  • [P2] Keep download defaults out of the config override root — internal/config/paths.go:396-424
    DriveDownloadsDir() and GmailAttachmentsDir() still derive from Dir(), so GOG_HOME, --home, or GOG_CONFIG_DIR move default downloads under the config override even though the new docs say downloads are unchanged by the XDG/GOG split.
    Confidence: 0.84

Overall correctness: patch is incorrect
Overall confidence: 0.87

Acceptance criteria:

  • go test ./internal/config ./internal/cmd ./internal/tracking
  • make lint

What I checked:

  • Protected author association: The GitHub context marks the PR author association as COLLABORATOR, so this workflow should keep it open for maintainer handling rather than auto-closing it.
  • PR head pre-scan behavior: Latest PR head scans all args for --home before Kong parsing, so subcommand flag values that are literally --home or --home=... can be consumed as a global override. (internal/cmd/root.go:279, fa25a78977e7)
  • PR head Gmail watch path migration: gmailWatchStatePath returns an existing legacy watch file as the store path, so subsequent Save calls keep writing to the legacy config-tree file instead of migrating writes to the new state root. (internal/cmd/gmail_watch_state.go:42, fa25a78977e7)
  • PR head download default conflict: The branch docs say downloads are unchanged by the XDG/GOG split, but DriveDownloadsDir and GmailAttachmentsDir still derive from Dir(), which now honors GOG_HOME, --home, and GOG_CONFIG_DIR. (internal/config/paths.go:396, fa25a78977e7)
  • Documentation statement: docs/paths.md explicitly says Drive downloads and Gmail attachments keep existing default directories unless --out is used. (docs/paths.md:41, fa25a78977e7)
  • Current-main ownership history: Current main's central path and CLI files are attributed by blame to the v0.18.0 release commit, and recent auth/path-sensitive history also points to Peter Steinberger's work in auth hardening and keyring locking. (internal/config/paths.go:14, 7c511d8dd731)

Likely related people:

  • Peter Steinberger: git blame attributes the central current-main path resolver, root CLI wiring, and Gmail watch state files to the v0.18.0 release commit, and recent auth/path-sensitive commits are also by this author. (role: current-main path and CLI owner; confidence: high; commits: 7c511d8dd731, 1531013e50b6, f3d57535ba53; files: internal/config/paths.go, internal/cmd/root.go, internal/cmd/gmail_watch_state.go)
  • alexminza: The PR body credits the original XDG/GOG path proposal and direction to this person via the linked feature issues, though current-main code ownership is not established from local history. (role: proposal author; confidence: medium; files: docs/paths.md, internal/config/paths.go)

Codex review notes: model gpt-5.5, reasoning high; reviewed against f4dbfd5cbf95.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels May 22, 2026
@clawsweeper

clawsweeper Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🔥 Warming up: real-behavior proof passed; findings, security review, or rank-up moves are still in progress.

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.
What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30f5d455d5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/cmd/root.go
Comment on lines 335 to 338
func globalFlagTakesValue(flag string) bool {
switch flag {
case "--color", "--account", "--acct", "--client", "--enable-commands", "--disable-commands", "--select", "--pick", "--project", "-a":
case "--color", "--account", "--acct", "--client", "--enable-commands", "--disable-commands", "--select", "--pick", "--project", "--home", "-a":
return true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include --access-token when pre-scanning --home args

preScanHomeArg depends on globalFlagTakesValue to skip values for earlier global flags, but --access-token is missing from this list. With inputs like gog --access-token TOKEN --home /tmp --help, the scanner treats TOKEN as the first positional token and stops before seeing --home, so the home override is not applied before helpDescription() is built; this makes help/introspection output report the wrong config/keyring paths despite --home being provided.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels May 22, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 16b0311bf4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/config/paths.go
Comment on lines +459 to +462
if _, primaryErr := os.Stat(primary); os.IsNotExist(primaryErr) {
if st, legacyErr := os.Stat(legacy); legacyErr == nil && st.IsDir() {
return legacy, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prefer new Gmail state root for writes

When a legacy .../state/gmail-watch directory exists, this branch returns that legacy directory instead of the new state root, so newGmailWatchStore(...).Save() keeps writing watch files to the old config tree indefinitely. In an upgraded install, this prevents migration to the new state location and contradicts the documented "legacy read fallback" behavior (new writes should land in the new path).

Useful? React with 👍 / 👎.

@steipete
steipete force-pushed the feat/xdg-gog-home-paths branch from 16b0311 to f22d03e Compare May 22, 2026 13:19
@alexminza

Copy link
Copy Markdown
Contributor

Thank you @steipete for your relentless dedication and effort to improve the projects and the world around you! Danke schön!

@clawsweeper clawsweeper Bot added the merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. label May 22, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fa25a78977

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/cmd/root.go
Comment on lines +294 to +297
if strings.HasPrefix(arg, "-") {
if globalFlagTakesValue(arg) && i+1 < len(args) {
i++
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict pre-scan to true global flag positions

preScanHomeArg() skips values only for flags listed in globalFlagTakesValue, so any subcommand flag value token is still scanned as if it were a top-level flag. With inputs like gog gmail send --subject --home --to [email protected] --body hi, --home is the subject value but this pre-scan treats it as a global --home override and tries to use --to as the path, causing an early usage error before normal parsing. This makes valid commands fail whenever a subcommand value is literally --home or --home=....

Useful? React with 👍 / 👎.

@steipete
steipete merged commit 23e74af into main May 22, 2026
10 checks passed
@steipete
steipete deleted the feat/xdg-gog-home-paths branch May 22, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

2 participants