Skip to content

fix: resolve concurrency races in RuntimeContext#330

Merged
liangshuo-1 merged 2 commits intomainfrom
fix/runtime-context-concurrency
Apr 8, 2026
Merged

fix: resolve concurrency races in RuntimeContext#330
liangshuo-1 merged 2 commits intomainfrom
fix/runtime-context-concurrency

Conversation

@liangshuo-1
Copy link
Copy Markdown
Collaborator

@liangshuo-1 liangshuo-1 commented Apr 8, 2026

Summary

Fix RuntimeContext concurrency hazards in shortcut execution by making API client initialization and deferred jq error capture safe for concurrent access.

Changes

  • Replace check-then-act API client caching with sync.OnceValues in RuntimeContext.
  • Use NewAPIClientWithConfig during lazy API client initialization so the resolved context config is applied at construction time.
  • Guard first jq output error capture with sync.Once.

Test Plan

  • Unit tests pass: make unit-test in clean temporary worktree at /tmp/larkcli-pr-verify-runtime-context
  • Manual local verification confirms the lark xxx command works as expected: N/A, internal concurrency helper change covered by unit tests

Related Issues

  • None

Summary by CodeRabbit

  • Refactor
    • Improved API client initialization and made runtime error capture thread-safe for more reliable behavior.
  • Bug Fixes
    • Adjusted authentication handling to use the appropriate token path for tenant-scoped requests.
  • Tests
    • Added a test verifying tenant access token resolution via the configured credential provider.

- getAPIClient: replace check-then-act with sync.OnceValues, matching
  the factory_default.go convention; use NewAPIClientWithConfig to avoid
  post-construction config override; fall back to direct construction
  for test contexts that bypass newRuntimeContext.

- outputErr: guard first-error capture with sync.Once to prevent data
  races if Out() is ever called from concurrent goroutines.

Change-Id: I99c94c3dcb7663fa61571c9720163e41a5fc0e36
@github-actions github-actions bot added the size/M Single-domain feat or fix with limited business impact label Apr 8, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

RuntimeContext in shortcuts/common/runner.go is refactored for thread-safe error capture and deferred API client initialization. cmd/auth/auth.go now obtains API clients differently and routes SDK calls through ac.DoSDKRequest with core.AsBot. cmd/auth/auth_test.go adds a test ensuring tenant access tokens are resolved via the credential provider.

Changes

Cohort / File(s) Summary
Runtime context (thread-safety)
shortcuts/common/runner.go
Replaced nil-check error handling with outputErrOnce.Do(...) for one-time error capture. Reworked API client caching into a deferred, thread-safe apiClientFunc created with sync.OnceValues in newRuntimeContext; getAPIClient() calls this func and falls back to Factory.NewAPIClientWithConfig(...) when absent.
Auth command implementation
cmd/auth/auth.go
Switched from f.LarkClient() to f.NewAPIClient() for API client retrieval. Replaced direct SDK invocation (sdk.Do) with ac.DoSDKRequest, passing core.AsBot for token context; retained HTTP method/path, params, and response parsing logic.
Auth tests
cmd/auth/auth_test.go
Added TestAuthScopesRun_UsesTenantAccessTokenFromCredentialProvider and authScopesTokenResolver stub. Test configures a CredentialProvider, stubs the app-info endpoint, runs authScopesRun (JSON output), and asserts tenant token resolution and Authorization header usage. Added imports (context, net/http, credential, httpmock).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇
I hopped through code with careful paws,
Added once and healed the flaws.
Clients deferred, tokens checked right,
Concurrency sleeps sound tonight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 'fix: resolve concurrency races in RuntimeContext' accurately summarizes the main change—addressing concurrency hazards in RuntimeContext with thread-safe patterns.
Description check ✅ Passed The PR description includes all required template sections: Summary (motivation and scope), Changes (main modifications listed), Test Plan (unit tests and manual verification), and Related Issues.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/runtime-context-concurrency

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 8, 2026

Greptile Summary

This PR resolves two concurrency hazards in RuntimeContext: API client initialization is now memoized via sync.OnceValues (replacing a bare check-then-act pattern), and the first jq-filter error is captured atomically via sync.Once. A separate commit migrates getAppInfo in auth.go to use NewAPIClient().DoSDKRequest(core.AsBot), aligning it with the rest of the codebase and adding a regression test that verifies the tenant token is resolved through the credential provider.

Confidence Score: 5/5

PR is safe to merge — the concurrency fixes are correct and the behavioral change in auth.go is properly tested.

No P0 or P1 findings. The sync.OnceValues and sync.Once usage is idiomatic and correct. The auth.go migration maintains equivalent token-scoping behavior. The new regression test provides direct coverage of the changed auth path. All prior thread concerns are either addressed or intentionally deferred.

No files require special attention.

Vulnerabilities

No security concerns identified. The migration to explicit core.AsBot in getAppInfo correctly scopes the request to tenant access tokens, and credential resolution goes through the CredentialProvider rather than bypassing it.

Important Files Changed

Filename Overview
shortcuts/common/runner.go Concurrency fixes: replaces check-then-act API client caching with sync.OnceValues; guards first jq-error capture in Out() with sync.Once; fallback path for test contexts remains intentionally un-cached.
cmd/auth/auth.go Migrates getAppInfo from f.LarkClient().Do() to f.NewAPIClient().DoSDKRequest(core.AsBot), making auth consistent with the rest of the codebase; DoSDKRequest sets SupportedAccessTokenTypes for tenant implicitly.
cmd/auth/auth_test.go Adds TestAuthScopesRun_UsesTenantAccessTokenFromCredentialProvider; verifies exactly one TAT resolution and correct Authorization header on the mocked HTTP stub.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[runShortcut] --> B[newRuntimeContext]
    B --> C["apiClientFunc = sync.OnceValues(...)"]
    B --> D[larkSDK eagerly initialized]
    A --> E[s.Execute called]
    E --> F["getAPIClient()"]
    F --> G{apiClientFunc != nil?}
    G -- yes --> H["apiClientFunc() — runs once, cached"]
    G -- no --> I["Factory.NewAPIClientWithConfig(ctx.Config) — test fallback"]
    E --> J["Out() / OutFormat() with JqExpr"]
    J --> K["JqFilter error?"]
    K -- yes --> M["outputErrOnce.Do → write outputErr once"]
    A --> N["return rctx.outputErr"]
Loading

Reviews (2): Last reviewed commit: "fix: use tenant token for auth scopes" | 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.

🧹 Nitpick comments (1)
shortcuts/common/runner.go (1)

80-87: Fallback path lacks thread-safety for test contexts.

When apiClientFunc is nil (test contexts bypassing newRuntimeContext), the fallback calls Factory.NewAPIClientWithConfig directly without caching. If tests invoke getAPIClient() concurrently on the same RuntimeContext, each call creates a new APIClient without synchronization.

This is likely intentional for simple test helpers, but worth documenting to prevent future test authors from assuming thread-safety in all code paths.

📝 Consider adding a comment to clarify the fallback behavior
 // getAPIClient returns the cached APIClient, creating it on first use.
 // Thread-safe via sync.OnceValues (initialized in newRuntimeContext).
-// Falls back to direct construction for test contexts that bypass newRuntimeContext.
+// Falls back to direct construction for test contexts that bypass newRuntimeContext;
+// the fallback is NOT thread-safe if called concurrently.
 func (ctx *RuntimeContext) getAPIClient() (*client.APIClient, error) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/common/runner.go` around lines 80 - 87, The fallback in
RuntimeContext.getAPIClient (when apiClientFunc == nil) calls
Factory.NewAPIClientWithConfig directly and is not synchronized, so concurrent
test calls can create multiple APIClient instances; update the function's
comment to explicitly state this non-thread-safe fallback behavior, mention that
newRuntimeContext (and sync.OnceValues) provides the thread-safe path, and
advise test authors to set apiClientFunc or construct the context via
newRuntimeContext if they need thread-safety.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@shortcuts/common/runner.go`:
- Around line 80-87: The fallback in RuntimeContext.getAPIClient (when
apiClientFunc == nil) calls Factory.NewAPIClientWithConfig directly and is not
synchronized, so concurrent test calls can create multiple APIClient instances;
update the function's comment to explicitly state this non-thread-safe fallback
behavior, mention that newRuntimeContext (and sync.OnceValues) provides the
thread-safe path, and advise test authors to set apiClientFunc or construct the
context via newRuntimeContext if they need thread-safety.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fe202db6-78b6-4156-80b7-8350afacd47d

📥 Commits

Reviewing files that changed from the base of the PR and between f5a8fbf and 867f29e.

📒 Files selected for processing (1)
  • shortcuts/common/runner.go

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@d55afe2d39dab27a8c1600f32e75c97892a3b0f2

🧩 Skill update

npx skills add larksuite/cli#fix/runtime-context-concurrency -y -g

Change-Id: I83bb677e9a33e906e207679b2ba8d0364bc20fe3
@github-actions github-actions bot added size/L Large or sensitive change across domains or core paths and removed size/M Single-domain feat or fix with limited business impact labels Apr 8, 2026
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

🤖 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/auth_test.go`:
- Around line 239-245:
TestAuthScopesRun_UsesTenantAccessTokenFromCredentialProvider can be influenced
by a real CLI config; before creating the stub credential provider in that test,
call t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) to isolate CLI config
state so credential.NewCredentialProvider will use the injected
authScopesTokenResolver instead of any real config; place this call at the start
of TestAuthScopesRun_UsesTenantAccessTokenFromCredentialProvider just before
setting f.Credential.
🪄 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: a86cc5af-e5ed-462a-96f1-32493c229237

📥 Commits

Reviewing files that changed from the base of the PR and between 867f29e and d55afe2.

📒 Files selected for processing (2)
  • cmd/auth/auth.go
  • cmd/auth/auth_test.go

@liangshuo-1 liangshuo-1 merged commit 555722a into main Apr 8, 2026
15 checks passed
@liangshuo-1 liangshuo-1 deleted the fix/runtime-context-concurrency branch April 8, 2026 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants