Skip to content

refactor: use XDG base dirs for config and data#410

Open
JayYoung2021 wants to merge 1 commit intolarksuite:mainfrom
JayYoung2021:refactor/adopt-xdg-for-config-and-data
Open

refactor: use XDG base dirs for config and data#410
JayYoung2021 wants to merge 1 commit intolarksuite:mainfrom
JayYoung2021:refactor/adopt-xdg-for-config-and-data

Conversation

@JayYoung2021
Copy link
Copy Markdown

@JayYoung2021 JayYoung2021 commented Apr 11, 2026

Summary

This change centralizes application directory resolution behind a new internal/appdir package that
follows XDG base directory conventions for config, cache, state, data, and log storage. It replaces
several package-specific path implementations with shared helpers so directory handling is more
consistent, easier to maintain, and better aligned across Linux and macOS.

Changes

• Add internal/appdir with XDG-compliant directory helpers, support for environment variable
overrides, and unit coverage for directory resolution behavior.
• Refactor keychain, lockfile, registry, update, auth, and core config code paths to use
centralized appdir helpers instead of ad hoc config/data path logic.
• Update related auth and keychain handling to consume the new directory APIs while preserving
existing behavior for persisted state and cached artifacts.

Test Plan

  • Unit tests pass
  • Manual local verification confirms the lark xxx command works as expected

Related Issues

• None

Summary by CodeRabbit

  • Refactor

    • Centralized CLI storage locations (config/cache/state/data/log) and standardized on-disk permission handling; safer env-override validation and consistent fallback behavior.
  • New Features

    • Added service-name validation to prevent unsafe service identifiers.
    • Diagnostic/status output and warnings now respect configured error/output streams.
  • Bug Fixes

    • Improved error handling around auth flows, token refresh/locks, keychain storage, and remote-cache read/write to reduce failures.
  • Tests

    • Added comprehensive tests for directory resolution, service-name validation, keychain and cache behaviors.

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


yi.yang08 seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions github-actions bot added the size/XL Architecture-level or global-impact change label Apr 11, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Centralizes CLI filesystem path logic into a new appdir package; migrates callers to use it; converts numeric permission literals to Go octal (0o...); normalizes/threads diagnostic writers (io.Discard); changes several APIs to propagate errors for data/storage dirs; and updates tests accordingly.

Changes

Cohort / File(s) Summary
New appdir package
internal/appdir/appdir.go, internal/appdir/appdir_test.go
Adds centralized path helpers: ConfigDir, ConfigPath, CacheDir, StateDir, LogDir, DataDir(service) with env/XDG/legacy/default precedence and comprehensive tests.
Auth diagnostics & signatures
internal/auth/app_registration.go, internal/auth/device_flow.go, internal/auth/uat_client.go, internal/auth/uat_client_options_test.go, internal/auth/transport.go
Removes/ignores some errOut params, introduces diagnosticWriter to normalize nil to io.Discard, and adjusts transport JSON-handler invocation condition.
Core config & login cache
internal/core/config.go, cmd/auth/login_scope_cache.go
Replaces ad-hoc config/cache path logic with appdir calls and updates permission literals to Go octal (0o700/0o600).
Keychain & auth logs
internal/keychain/keychain_darwin.go, internal/keychain/keychain_other.go, internal/keychain/auth_log.go, tests...
Delegates storage/log directory resolution to appdir (DataDir/LogDir), changes StorageDir to return (string, error), propagates errors to callers, and standardizes permission literals and log writer behavior.
Lockfile, registry, update state
internal/lockfile/lockfile.go, internal/registry/remote.go, internal/registry/loader.go, internal/update/update.go, internal/registry/remote_test.go
Moves lock/cache/state roots to appdir locations, parameterizes some functions with io.Writer for status output, updates permission literals, and adjusts tests to isolated cache/state dirs.
Factory/HTTP client wiring & tests
internal/cmdutil/factory_default.go, internal/cmdutil/factory_http_test.go
Threads IOStreams.ErrOut into cached HTTP client factory to route proxy warnings to the provided writer; tests updated to pass a writer.
Validation and VFS service name
internal/validate/path.go, internal/vfs/localfileio/path.go, internal/vfs/localfileio/path_test.go
Adds SafeServiceName validation (rejects empty, traversal tokens, separators, control chars) and tests; normalizes test permission literals and error handling.
Misc. permission/format normalizations
various files (internal/core/*, internal/keychain/*, internal/lockfile/*, etc.)
Uniform conversion of numeric permission literals to Go octal form (0o700, 0o600, 0o644, etc.) and removal of duplicated ad-hoc path resolution logic in favor of appdir.

Sequence Diagram(s)

(Skipped)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

domain/base

Suggested reviewers

  • liangshuo-1
  • tuxedomm
  • MaxHuang22

Poem

🐰 I hopped through folders, neat and spry,
Appdir mapped each hidden sky,
Octal hops and writers bright,
Storage, locks and logs set right,
A tidy warren — code feels light!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: refactoring to use XDG base directories for config and data storage, which is the core objective of this PR.
Description check ✅ Passed The description follows the template with all required sections: Summary (explaining centralized directory resolution with XDG conventions), Changes (bullet-pointed list of key modifications), Test Plan (both unit tests and manual verification marked complete), and Related Issues (noting none). All sections are populated with meaningful content.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 11, 2026

Greptile Summary

This PR extracts all application directory resolution into a new internal/appdir package following XDG base directory conventions (config, cache, state, log, data), replaces ad hoc path logic across keychain, lockfile, registry, update, and auth subsystems, and adds service-name validation to guard path construction. The env-var-without-validation concern and the missing user-facing warning from the previous review thread have both been resolved in this version.

Confidence Score: 5/5

Safe to merge — all P0/P1 concerns from the prior review thread have been addressed and no new critical issues were found.

The two P1 findings from the previous thread (env vars accepted without validation in ConfigDir/CacheDir/StateDir; silent validation failure with no user feedback) are both fixed in this revision. The remaining open question (legacy data migration for non-Darwin platforms) was already discussed in the previous thread. The only new finding is a P2 style issue with a redundant control-character check in SafeServiceName. All path changes are backward-compatible for existing users thanks to the legacyConfigDir fallback preserving the ~/.lark-cli base.

internal/appdir/appdir.go — legacyDataDir only covers Darwin; non-Darwin upgrade migration was raised in the prior thread.

Important Files Changed

Filename Overview
internal/appdir/appdir.go New package providing XDG-compliant directory resolution with validated env var overrides, legacy-dir fallback, and per-platform defaults; legacyDataDir only handles Darwin, leaving Linux users with existing data at ~/.local/share/<service> without a migration path.
internal/appdir/appdir_test.go Comprehensive tests covering env-var override precedence, XDG fallback, legacy-dir detection, service-name validation, and platform-specific defaults.
internal/vfs/localfileio/path.go Adds SafeServiceName validation function; the explicit \t\n\r check (line 138) is redundant with the subsequent charcheck.RejectControlChars call but causes no correctness issue.
internal/keychain/keychain_darwin.go Delegates StorageDir to appdir.DataDir; platformSet correctly moves MkdirAll inside the error branch — the success path guarantees dir already exists.
internal/lockfile/lockfile.go Lock-file directory moved from {configDir}/locks to {stateDir}/locks; backward-compatible for existing users because StateDir() returns ~/.lark-cli when the legacy dir is present.
internal/registry/loader.go Threads errOut io.Writer into InitWithBrand; Init() correctly passes io.Discard (used only in tests), while all production callers use InitWithBrand with the proper stream.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Directory request e.g. ConfigDir] --> B{LARKSUITE_CLI_*_DIR set?}
    B -- valid absolute path --> C[Use override dir]
    B -- invalid / relative --> D[Print warning, fall through]
    B -- not set --> E{XDG_*_HOME set?}
    D --> E
    E -- valid --> F[Use XDG base + appName]
    E -- invalid --> G[Print warning, fall through]
    E -- not set --> H{~/.lark-cli exists?}
    G --> H
    H -- yes --> I[Use legacy dir ~/.lark-cli]
    H -- no --> J[Use platform default e.g. ~/.config/lark-cli]
Loading

Reviews (7): Last reviewed commit: "refactor: adopt XDG base directory speci..." | Re-trigger Greptile

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/auth/app_registration.go (1)

43-43: Remove the ignored errOut argument from RequestAppRegistration.

Line 43 keeps a writer parameter but discards it, while callers (e.g., cmd/config/init_interactive.go) still pass f.IOStreams.ErrOut. This is a confusing API contract; either use it or remove it from signature and call sites.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/auth/app_registration.go` at line 43, The RequestAppRegistration
function currently declares an unused writer parameter (errOut)—remove that
parameter from RequestAppRegistration's signature and all call sites (e.g.,
where cmd/config/init_interactive.go passes f.IOStreams.ErrOut) so callers
invoke RequestAppRegistration(httpClient, brand) instead; update any
imports/usages accordingly and run tests/compile to find and fix any remaining
references to the removed parameter name.
internal/lockfile/lockfile.go (1)

31-33: Update the lock path docstring to reflect StateDir.

The comment says {configDir}/locks/..., but Line 42 now builds the directory from appdir.StateDir().

Also applies to: 42-42

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/lockfile/lockfile.go` around lines 31 - 33, The docstring for
ForSubscribe is out of date: it references "{configDir}/locks/..." but the code
constructs the path from appdir.StateDir(); update the comment for ForSubscribe
to state the lock path uses StateDir (e.g.,
"{stateDir}/locks/subscribe_{appID}.lock") and any similar docstring lines so
they match the actual implementation that calls appdir.StateDir().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/appdir/appdir.go`:
- Around line 21-23: The code currently returns raw
os.Getenv("LARKSUITE_CLI_CONFIG_DIR") in the config/cache/state path getters,
bypassing safe-path checks; change those getters to validate the environment
value before use by either reusing the existing validatedEnvDir helper (like
LogDir/DataDir do) or by passing the env value through validate.SafeInputPath
and returning the sanitized/fallback path when validation fails; update the
functions that read LARKSUITE_CLI_CONFIG_DIR (the config/cache/state path
getters in appdir.go) to perform this validation instead of returning the raw
env string.

In `@internal/registry/remote.go`:
- Line 239: The progress message is currently written via
fmt.Fprintln(log.Writer(), "Fetching API metadata...") which can send progress
to stdout if logs are redirected; replace that call to write explicitly to
stderr (e.g. fmt.Fprintln(os.Stderr, "Fetching API metadata...")) and add the
necessary import for os so progress/warning output is always on stderr rather
than the log writer.

---

Nitpick comments:
In `@internal/auth/app_registration.go`:
- Line 43: The RequestAppRegistration function currently declares an unused
writer parameter (errOut)—remove that parameter from RequestAppRegistration's
signature and all call sites (e.g., where cmd/config/init_interactive.go passes
f.IOStreams.ErrOut) so callers invoke RequestAppRegistration(httpClient, brand)
instead; update any imports/usages accordingly and run tests/compile to find and
fix any remaining references to the removed parameter name.

In `@internal/lockfile/lockfile.go`:
- Around line 31-33: The docstring for ForSubscribe is out of date: it
references "{configDir}/locks/..." but the code constructs the path from
appdir.StateDir(); update the comment for ForSubscribe to state the lock path
uses StateDir (e.g., "{stateDir}/locks/subscribe_{appID}.lock") and any similar
docstring lines so they match the actual implementation that calls
appdir.StateDir().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e9dc249-4cc7-436d-90d7-8aa3a1b02325

📥 Commits

Reviewing files that changed from the base of the PR and between a00dfad and ecbb1fb.

📒 Files selected for processing (15)
  • cmd/auth/login_scope_cache.go
  • internal/appdir/appdir.go
  • internal/appdir/appdir_test.go
  • internal/auth/app_registration.go
  • internal/auth/device_flow.go
  • internal/auth/transport.go
  • internal/auth/uat_client.go
  • internal/auth/uat_client_options_test.go
  • internal/core/config.go
  • internal/keychain/auth_log.go
  • internal/keychain/keychain_darwin.go
  • internal/keychain/keychain_other.go
  • internal/lockfile/lockfile.go
  • internal/registry/remote.go
  • internal/update/update.go

@JayYoung2021 JayYoung2021 force-pushed the refactor/adopt-xdg-for-config-and-data branch from ecbb1fb to 3997c66 Compare April 11, 2026 17:01
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
internal/update/update.go (1)

163-164: Use one computed path in saveState() to avoid split mkdir/write targets.

Line 163 derives dir once, but Line 171 recomputes statePath(). Building the file path from dir keeps both operations consistent.

♻️ Proposed refactor
 func saveState(s *updateState) error {
 	dir := appdir.StateDir()
+	path := filepath.Join(dir, stateFile)
 	if err := vfs.MkdirAll(dir, 0o700); err != nil {
 		return err
 	}
 	data, err := json.Marshal(s)
 	if err != nil {
 		return err
 	}
-	return validate.AtomicWrite(statePath(), data, 0o644)
+	return validate.AtomicWrite(path, data, 0o644)
 }

Also applies to: 171-171

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/update/update.go` around lines 163 - 164, In saveState(), avoid
recomputing the path twice: call appdir.StateDir() once, derive the full state
path via statePath(dir) (or build the file path from dir) and use that single
computed path for the vfs.MkdirAll and subsequent write operations; update
references to statePath() in saveState() so both the mkdir and file-write target
the same path and remove the redundant recomputation.
internal/registry/remote_test.go (1)

138-139: Fail fast on fixture setup errors in tests.

os.MkdirAll / os.WriteFile return values are ignored in several setup blocks. Please assert these errors to avoid opaque test failures.

Also applies to: 158-163, 195-200, 413-417, 459-463

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/registry/remote_test.go` around lines 138 - 139, The test fixture
setup currently ignores errors from os.MkdirAll and os.WriteFile (e.g., the
calls in internal/registry/remote_test.go that create cDir and write
remote_meta.json); change each ignored call to capture the returned error and
assert it (e.g., use t.Fatalf or require.NoError) so setup failures fail fast
and reveal the underlying error; apply this fix to every occurrence of
os.MkdirAll/os.WriteFile in the file including the blocks noted (around the
shown call and at the ranges 158-163, 195-200, 413-417, 459-463).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/vfs/localfileio/path_test.go`:
- Line 111: The test fixture setup currently ignores errors from os.MkdirAll and
os.WriteFile which can hide setup failures; update the two offending calls that
create the "real" directory and write fixture files to check their returned
error and call t.Fatalf (or t.Fatal) with a clear message including the error
(e.g., t.Fatalf("os.MkdirAll(...): %v", err) / t.Fatalf("os.WriteFile(...): %v",
err)). Ensure both occurrences are updated so test setup fails fast with the
underlying error instead of proceeding.

---

Nitpick comments:
In `@internal/registry/remote_test.go`:
- Around line 138-139: The test fixture setup currently ignores errors from
os.MkdirAll and os.WriteFile (e.g., the calls in
internal/registry/remote_test.go that create cDir and write remote_meta.json);
change each ignored call to capture the returned error and assert it (e.g., use
t.Fatalf or require.NoError) so setup failures fail fast and reveal the
underlying error; apply this fix to every occurrence of os.MkdirAll/os.WriteFile
in the file including the blocks noted (around the shown call and at the ranges
158-163, 195-200, 413-417, 459-463).

In `@internal/update/update.go`:
- Around line 163-164: In saveState(), avoid recomputing the path twice: call
appdir.StateDir() once, derive the full state path via statePath(dir) (or build
the file path from dir) and use that single computed path for the vfs.MkdirAll
and subsequent write operations; update references to statePath() in saveState()
so both the mkdir and file-write target the same path and remove the redundant
recomputation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6fe62927-9c2c-464b-bf79-41c3bd31e888

📥 Commits

Reviewing files that changed from the base of the PR and between ecbb1fb and 3997c66.

📒 Files selected for processing (22)
  • cmd/auth/login_scope_cache.go
  • internal/appdir/appdir.go
  • internal/appdir/appdir_test.go
  • internal/auth/app_registration.go
  • internal/auth/device_flow.go
  • internal/auth/transport.go
  • internal/auth/uat_client.go
  • internal/auth/uat_client_options_test.go
  • internal/cmdutil/factory_default.go
  • internal/cmdutil/factory_http_test.go
  • internal/core/config.go
  • internal/keychain/auth_log.go
  • internal/keychain/keychain_darwin.go
  • internal/keychain/keychain_other.go
  • internal/lockfile/lockfile.go
  • internal/registry/loader.go
  • internal/registry/remote.go
  • internal/registry/remote_test.go
  • internal/update/update.go
  • internal/validate/path.go
  • internal/vfs/localfileio/path.go
  • internal/vfs/localfileio/path_test.go
✅ Files skipped from review due to trivial changes (3)
  • internal/lockfile/lockfile.go
  • internal/auth/uat_client.go
  • internal/auth/device_flow.go
🚧 Files skipped from review as they are similar to previous changes (9)
  • cmd/auth/login_scope_cache.go
  • internal/auth/uat_client_options_test.go
  • internal/keychain/auth_log.go
  • internal/keychain/keychain_other.go
  • internal/core/config.go
  • internal/auth/app_registration.go
  • internal/keychain/keychain_darwin.go
  • internal/appdir/appdir.go
  • internal/appdir/appdir_test.go

@JayYoung2021 JayYoung2021 force-pushed the refactor/adopt-xdg-for-config-and-data branch 2 times, most recently from 8b01e82 to eb8a7ba Compare April 11, 2026 19:56
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/appdir/appdir.go`:
- Around line 114-120: xdgDataDir and defaultDataDir currently join paths as
<base>/<service>, causing XDG spec and collision issues; update both functions
to namespace under the appName constant by joining as <base>/<appName>/<service>
(or <base>/<appName> when service is empty) so paths match
ConfigDir/CacheDir/StateDir behavior and avoid collisions—locate the xdgDataDir
and defaultDataDir functions and change their filepath.Join logic to include
appName between base and service.

In `@internal/vfs/localfileio/path.go`:
- Around line 125-142: SafeServiceName currently relies on
charcheck.RejectControlChars which allows tab/newline characters; update
SafeServiceName to explicitly reject ASCII horizontal tab and newline (and
carriage return) so names cannot contain embedded tabs/newlines. Add a check in
SafeServiceName (after the empty/traversal/separator checks, before or after
calling charcheck.RejectControlChars) that detects any of '\t', '\n', '\r' (or
use strings.ContainsAny(name, "\t\n\r")) and returns a clear error like "service
name must not contain tabs or newlines". Keep the existing call to
charcheck.RejectControlChars for other control chars.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a7ce3656-2dce-4d0f-bac8-a35f84422e88

📥 Commits

Reviewing files that changed from the base of the PR and between 8b01e82 and eb8a7ba.

📒 Files selected for processing (24)
  • cmd/auth/login_scope_cache.go
  • internal/appdir/appdir.go
  • internal/appdir/appdir_test.go
  • internal/auth/app_registration.go
  • internal/auth/device_flow.go
  • internal/auth/transport.go
  • internal/auth/uat_client.go
  • internal/auth/uat_client_options_test.go
  • internal/cmdutil/factory_default.go
  • internal/cmdutil/factory_http_test.go
  • internal/core/config.go
  • internal/keychain/auth_log.go
  • internal/keychain/keychain_darwin.go
  • internal/keychain/keychain_darwin_test.go
  • internal/keychain/keychain_other.go
  • internal/keychain/keychain_other_test.go
  • internal/lockfile/lockfile.go
  • internal/registry/loader.go
  • internal/registry/remote.go
  • internal/registry/remote_test.go
  • internal/update/update.go
  • internal/validate/path.go
  • internal/vfs/localfileio/path.go
  • internal/vfs/localfileio/path_test.go
✅ Files skipped from review due to trivial changes (3)
  • internal/lockfile/lockfile.go
  • internal/registry/remote.go
  • internal/cmdutil/factory_default.go
🚧 Files skipped from review as they are similar to previous changes (9)
  • internal/auth/transport.go
  • internal/auth/uat_client_options_test.go
  • internal/cmdutil/factory_http_test.go
  • internal/keychain/auth_log.go
  • internal/auth/device_flow.go
  • internal/registry/remote_test.go
  • internal/keychain/keychain_other.go
  • internal/auth/app_registration.go
  • internal/appdir/appdir_test.go

@JayYoung2021 JayYoung2021 force-pushed the refactor/adopt-xdg-for-config-and-data branch from eb8a7ba to 782032b Compare April 11, 2026 21:49
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/keychain/keychain_darwin.go (1)

280-295: ⚠️ Potential issue | 🟠 Major

Fail fast on storage-dir errors before touching the macOS keychain.

platformSet can call getMasterKey(service, true) before it proves that StorageDir(service) is valid and the destination directory can be created. If either later step fails, the method returns an error after already creating master.key in the system keychain.

Suggested fix
 func platformSet(service, account, data string) error {
+	dir, err := StorageDir(service)
+	if err != nil {
+		return err
+	}
+	if err := vfs.MkdirAll(dir, 0o700); err != nil {
+		return err
+	}
+
 	key, err := getFileMasterKey(service, false)
 	if err != nil {
 		key, err = getMasterKey(service, true)
 		if err != nil {
 			key, err = getFileMasterKey(service, true)
 			if err != nil {
 				return err
 			}
 		}
 	}
-	dir, err := StorageDir(service)
-	if err != nil {
-		return err
-	}
-	if err := vfs.MkdirAll(dir, 0o700); err != nil {
-		return err
-	}
 	encrypted, err := encryptData(data, key)
 	if err != nil {
 		return err
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/keychain/keychain_darwin.go` around lines 280 - 295, platformSet
currently may create a system keychain master key (via getMasterKey(service,
true) / getFileMasterKey(service, true)) before validating the storage
directory; move the StorageDir(service) & vfs.MkdirAll(dir, 0o700) checks to
occur before any call that can create keys so failures fail fast. Concretely: in
platformSet, after attempting getFileMasterKey(service, false) and seeing it
missing, first call StorageDir(service) and ensure vfs.MkdirAll succeeds; only
then call getMasterKey(service, true) and the subsequent
getFileMasterKey(service, true) to create/write master.key. This prevents
creating master.key in the macOS keychain when storage setup fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/auth/login_scope_cache.go`:
- Around line 47-54: Before performing file I/O, validate the computed paths by
calling validate.SafeInputPath on the results of loginScopeCacheDir() and
loginScopeCachePath(deviceCode); if validation fails, return the error.
Specifically, call validate.SafeInputPath(loginScopeCacheDir()) before
vfs.MkdirAll(...) and call
validate.SafeInputPath(loginScopeCachePath(deviceCode)) before
validate.AtomicWrite(...), propagating any validation error instead of
proceeding with MkdirAll or AtomicWrite.

In `@internal/keychain/auth_log_test.go`:
- Around line 28-33: The test TestAuthLogDir_InvalidLogDirFallsBackToStateDir
ignores errors from filepath.EvalSymlinks and doesn't isolate config state;
update the test to check and handle the error returned by filepath.EvalSymlinks
instead of discarding it, and set LARKSUITE_CLI_CONFIG_DIR to t.TempDir() via
t.Setenv to ensure deterministic config-state isolation; locate the logic in
TestAuthLogDir_InvalidLogDirFallsBackToStateDir and modify the call to
filepath.EvalSymlinks to capture and fail the test on error, and add
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) before exercising the code.

In `@internal/keychain/auth_log.go`:
- Line 127: The panic warning currently writes to log.Writer(), which may not be
stderr; change the fmt.Fprintf call that prints "[lark-cli] [WARN] background
log cleanup panicked: %v\n" to explicitly write to os.Stderr (e.g.,
fmt.Fprintf(os.Stderr, ...)), and add the necessary os import; target the
fmt.Fprintf line in the background log cleanup panic handler so warnings are
guaranteed to go to stderr.

In `@internal/keychain/keychain_other_test.go`:
- Around line 35-41: The test fails to guarantee the HOME-based fallback because
XDG_DATA_HOME may be set in the environment; before calling StorageDir("svc") in
internal/keychain/keychain_other_test.go update the test to clear XDG_DATA_HOME
(e.g. use t.Setenv("XDG_DATA_HOME", "") or t.Unsetenv equivalent) so
StorageDir's HOME fallback branch (in internal/appdir/appdir.go) is exercised
and the expected `want` remains correct; keep the existing t.Setenv("HOME",
home) and StorageDir("svc") call as-is.

In `@internal/update/update.go`:
- Line 147: The computed state path produced by filepath.Join(appdir.StateDir(),
stateFile) is used directly in vfs.MkdirAll, vfs.ReadFile and
validate.AtomicWrite without validating it; before any of those calls (and
wherever the state path is constructed/returned), run validate.SafeInputPath on
the resulting path (or the directory part) and return/handle an error if
validation fails so you never call vfs.MkdirAll, vfs.ReadFile or
validate.AtomicWrite with an unsafe path; update the code paths around the state
path construction (the filepath.Join(appdir.StateDir(), stateFile) site and the
call sites that invoke vfs.MkdirAll, vfs.ReadFile, and validate.AtomicWrite) to
perform this validation first.

---

Outside diff comments:
In `@internal/keychain/keychain_darwin.go`:
- Around line 280-295: platformSet currently may create a system keychain master
key (via getMasterKey(service, true) / getFileMasterKey(service, true)) before
validating the storage directory; move the StorageDir(service) &
vfs.MkdirAll(dir, 0o700) checks to occur before any call that can create keys so
failures fail fast. Concretely: in platformSet, after attempting
getFileMasterKey(service, false) and seeing it missing, first call
StorageDir(service) and ensure vfs.MkdirAll succeeds; only then call
getMasterKey(service, true) and the subsequent getFileMasterKey(service, true)
to create/write master.key. This prevents creating master.key in the macOS
keychain when storage setup fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d4294b8-2b95-44d2-b93b-c976539193e8

📥 Commits

Reviewing files that changed from the base of the PR and between eb8a7ba and 782032b.

📒 Files selected for processing (28)
  • cmd/auth/login_scope_cache.go
  • cmd/config/init_interactive.go
  • internal/appdir/appdir.go
  • internal/appdir/appdir_test.go
  • internal/auth/app_registration.go
  • internal/auth/device_flow.go
  • internal/auth/transport.go
  • internal/auth/uat_client.go
  • internal/auth/uat_client_options_test.go
  • internal/cmdutil/factory_default.go
  • internal/cmdutil/factory_http_test.go
  • internal/core/config.go
  • internal/keychain/auth_log.go
  • internal/keychain/auth_log_test.go
  • internal/keychain/keychain_darwin.go
  • internal/keychain/keychain_darwin_test.go
  • internal/keychain/keychain_other.go
  • internal/keychain/keychain_other_test.go
  • internal/lockfile/lockfile.go
  • internal/lockfile/lockfile_test.go
  • internal/registry/loader.go
  • internal/registry/remote.go
  • internal/registry/remote_test.go
  • internal/update/update.go
  • internal/update/update_test.go
  • internal/validate/path.go
  • internal/vfs/localfileio/path.go
  • internal/vfs/localfileio/path_test.go
✅ Files skipped from review due to trivial changes (4)
  • internal/validate/path.go
  • internal/auth/transport.go
  • internal/core/config.go
  • internal/keychain/keychain_darwin_test.go
🚧 Files skipped from review as they are similar to previous changes (13)
  • internal/auth/uat_client_options_test.go
  • internal/lockfile/lockfile.go
  • internal/cmdutil/factory_http_test.go
  • internal/vfs/localfileio/path.go
  • internal/auth/app_registration.go
  • internal/registry/remote_test.go
  • internal/registry/remote.go
  • internal/vfs/localfileio/path_test.go
  • internal/cmdutil/factory_default.go
  • internal/auth/uat_client.go
  • internal/registry/loader.go
  • internal/appdir/appdir.go
  • internal/appdir/appdir_test.go

@JayYoung2021 JayYoung2021 force-pushed the refactor/adopt-xdg-for-config-and-data branch from 782032b to 88a7272 Compare April 11, 2026 22:08
Extract directory resolution into a new internal/appdir package that follows the XDG base directory specification. Config, cache, state, log, and per-service data directories now respect LARKSUITE_CLI_*_DIR environment variables and XDG_*_HOME fallbacks. Lock files are moved from the config directory to the state directory.
@JayYoung2021 JayYoung2021 force-pushed the refactor/adopt-xdg-for-config-and-data branch from 88a7272 to 3153520 Compare April 11, 2026 22:17
@JayYoung2021
Copy link
Copy Markdown
Author

JayYoung2021 commented Apr 11, 2026

@liangshuo-1 @kongenpei @infeng All review threads on this PR are resolved and the related tests are passing. Could you please take a look when you have a moment?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Architecture-level or global-impact change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants