Skip to content

fix(config/init): respect --brand flag in --new mode#711

Merged
liangshuo-1 merged 3 commits into
mainfrom
fix/brand-flag-in-new-mode
Apr 29, 2026
Merged

fix(config/init): respect --brand flag in --new mode#711
liangshuo-1 merged 3 commits into
mainfrom
fix/brand-flag-in-new-mode

Conversation

@liangshuo-1

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

Copy link
Copy Markdown
Collaborator

Summary

The --new flag in lark-cli config init was ignoring the --brand parameter and always passing core.BrandFeishu to runCreateAppFlow.

Fix

Changed init.go:272 from hardcoded core.BrandFeishu to parseBrand(opts.Brand), which correctly handles the user's --brand lark flag for international (Lark) deployments.

Testing

  • Build passes ✅
  • Existing unit tests pass ✅
  • parseBrand defaults to BrandFeishu when no brand is specified, so backward compatibility is preserved

Summary by CodeRabbit

Release Notes

  • New Features

    • Added --queries flag to contact +search-user command for parallel multi-query fanout searches (up to 20 queries, 50 characters each).
    • App creation flow now supports user-selected brand selection.
  • Documentation

    • Updated contact search user command documentation with new --queries usage, constraints, and output structure.

Add --queries CSV flag to lark-cli contact +search-user for parallel
multi-name fanout (up to 20 entries, partial-failure tolerant).

Output shape in fanout mode:
- data.users[] rows carry matched_query (string)
- data.queries[] sidecar lists each input with {query, error?, has_more}
- top-level data.has_more removed (per-query in queries[])
- error is omitempty; absent on success

Single --query mode is byte-for-byte unchanged (regression-guarded).
--queries is mutually exclusive with --query and --user-ids; bool
filters propagate to every sub-request.

Workers run with WaitGroup + buffered semaphore + index-slot writes;
each has defer recover() converting panics to internal error: ... in
the sidecar (no stack to stderr). Pre-canceled context returns
context canceled without making the request.

All-failed exit propagates first failure's HTTP/API code via ErrAPI;
falls back to ExitInternal for transport/parse/panic/ctx-canceled
(avoids emitting code 0, which means success in the Lark protocol).
HTTP non-200 ErrMsg now includes truncated response body for diagnosis.

Drive-by: signature field is now omitempty (mostly empty in practice).

Infrastructure:
- internal/httpmock gains BodyFilter/OnMatch/Reusable/CapturedBodies
  hooks to support concurrent stub-driven tests
- internal/output adds 'users' to knownArrayFields so CSV picks the
  primary array correctly

Change-Id: I3c14195fb8e094ae150002d90c36a0e4a0cc97d0
…ndFeishu in --new mode

The --new flag was ignoring the --brand flag and always passing BrandFeishu
to runCreateAppFlow. Now it correctly uses parseBrand(opts.Brand) to
respect the user's --brand parameter (e.g., --brand lark for international).

Change-Id: I1d4d78b3d586142b0210e6ceaeeb467b14e9c1a1
@github-actions github-actions Bot added domain/contact PR touches the contact domain size/L Large or sensitive change across domains or core paths labels Apr 29, 2026
@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request introduces a multi-query fanout capability for contact search (--queries flag), refactors HTTP request body handling in the mock registry with support for body-based matching and reusable stubs, adds "users" to recognized array fields for output formatting, updates the app creation flow to respect user-selected brand, and provides comprehensive test coverage and documentation for the new search behavior.

Changes

Cohort / File(s) Summary
App creation brand selection
cmd/config/init.go
Modified --new flow to pass user-selected brand to runCreateAppFlow instead of hardcoding core.BrandFeishu.
HTTP mocking enhancements
internal/httpmock/registry.go
Refactored request body handling to read once, restore for downstream consumers, and support body-based matching via BodyFilter. Added Reusable behavior to prevent stub reuse, OnMatch callback synchronous invocation, and CapturedBodies array for tracking all hits alongside the single most-recent CapturedBody. Updated matching logic and verification for reusable stubs.
Output formatting
internal/output/format.go
Extended knownArrayFields to include "users" so it gains priority in array extraction and affects table/CSV/NDJSON formatting output.
Contact search fanout feature
shortcuts/contact/contact_search_user.go, shortcuts/contact/contact_search_user_fanout.go
Introduced --queries flag for parallel multi-keyword search with CSV input, optional query body filters, and concurrent API execution. Added validation for mutual exclusivity, query count limits (max 20), per-query length limits (≤50 runes), and deduplication. Made Signature field JSON-optional (omitempty). Fanout implementation executes one API request per query with bounded concurrency, converts all failure modes to per-query error strings, and reassembles results in input order with matched_query propagation.
Contact search tests
shortcuts/contact/contact_search_user_test.go
Added 721 lines of test coverage: validation tests for --queries flag, unit tests for query parsing/dedup, integration tests for single/multi-query execution, error-handling tests for HTTP/API/unmarshal failures, concurrency assertion (peak of 5 workers), fanout assembly and ordering validation, and format-specific output hygiene (NDJSON, CSV, pretty-print).
Documentation
skills/lark-contact/references/lark-contact-search-user.md
Updated command docs for lark-cli contact +search-user to describe --queries mode: mutual-exclusion rules, new output structure (data.users[].matched_query, per-query error/has_more in data.queries[]), input constraints, and field behavior clarifications (e.g., signature is optional, has_more absent at top level in fanout mode).

Sequence Diagram(s)

sequenceDiagram
    participant User as User/CLI
    participant Parser as Query Parser
    participant Executor as Fanout Executor
    participant API as Lark API
    participant Assembler as Result Assembler
    participant Formatter as Output Formatter

    User->>Parser: --queries "alice,bob,charlie"
    Parser->>Parser: Trim, deduplicate, validate
    Parser->>Executor: [query1, query2, query3]

    par Concurrent Execution
        Executor->>API: POST search(query1)
        Executor->>API: POST search(query2)
        Executor->>API: POST search(query3)
    end

    API-->>Executor: {users: [...], has_more: true}
    API-->>Executor: {users: [...], has_more: false}
    API-->>Executor: Error or {users: [...]}

    Executor->>Assembler: Per-query results + errors
    Assembler->>Assembler: Add matched_query to users
    Assembler->>Assembler: Collect query metadata
    Assembler->>Assembler: Flatten user results

    Assembler->>Formatter: data.queries[], data.users[]
    Formatter->>User: Table/CSV/NDJSON output + stderr summary
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • liuxinyanglxy
  • hugang-lark

Poem

🐰 A rabbit hops through queries three,
Fanout searches wild and free!
Each body matched, each stub reused,
With mocks and brands so well-enthused,
The search now fans across the land,
Let parallel magic take command! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the bug fix: the --brand flag is now respected in --new mode for config initialization.
Description check ✅ Passed The description includes all key sections from the template with appropriate content: Summary explains the bug, Fix describes the solution, and Testing validates the changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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/brand-flag-in-new-mode

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@codecov

codecov Bot commented Apr 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 64.14%. Comparing base (f7a56f3) to head (de3d0f7).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
cmd/config/init.go 0.00% 1 Missing ⚠️

❌ Your patch status has failed because the patch coverage (0.00%) is below the target coverage (60.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #711   +/-   ##
=======================================
  Coverage   64.14%   64.14%           
=======================================
  Files         504      504           
  Lines       44285    44285           
=======================================
  Hits        28406    28406           
  Misses      13411    13411           
  Partials     2468     2468           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Apr 29, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

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

🧩 Skill update

npx skills add larksuite/cli#fix/brand-flag-in-new-mode -y -g

@coderabbitai coderabbitai 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.

Caution

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

⚠️ Outside diff range comments (2)
cmd/config/init.go (1)

270-288: ⚠️ Potential issue | 🟠 Major

Add a regression test for --new --brand pass-through.

This changes Mode 3 runtime behavior, but the diff doesn’t include a test that asserts runCreateAppFlow gets the parsed --brand value (e.g., lark) instead of defaulting to Feishu. Please add a focused unit test for this path.

I can draft the exact test case with a stubbed flow assertion if you want.
As per coding guidelines "Every behavior change must have an accompanying test".

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

In `@cmd/config/init.go` around lines 270 - 288, Add a focused unit/regression
test that exercises the Mode 3 path when opts.New is true and a --brand value is
provided: stub or mock runCreateAppFlow and assert it is invoked with the parsed
brand from parseBrand(opts.Brand) (e.g., "lark") rather than a fallback value;
the test should invoke the same call site logic as in init.go (the branch that
calls runCreateAppFlow(opts.Ctx, f, parseBrand(opts.Brand), msg)) and verify the
brand argument passed into the stubbed runCreateAppFlow, and also assert the
rest of the flow returns/handles the stubbed result appropriately (so
saveInitConfig and output.PrintJson behavior can be either no-ops or verified
via mocks).
internal/httpmock/registry.go (1)

101-125: ⚠️ Potential issue | 🟠 Major

Don't run BodyFilter under r.mu, and don't hand it the live body slice.

BodyFilter is arbitrary caller code, but it currently executes while the registry mutex is held and receives the shared capturedBody slice. That means a filter that re-enters Register, Verify, or another RoundTrip can deadlock the registry, and a mutating filter can rewrite the body seen by later stubs and the stored captures. Evaluate the predicate on a defensive copy outside the critical section, then re-lock only to claim/update the matched stub.

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

In `@internal/httpmock/registry.go` around lines 101 - 125, The BodyFilter is
invoked while holding r.mu and given the shared capturedBody slice; change match
in Registry so BodyFilter runs outside the critical section on a defensive copy:
while locked, iterate r.stubs and skip by Method/URL and already matched
(s.matched) to shortlist candidate stub pointers (or simply note the index),
then make a copy of the body (bodyCopy := append([]byte(nil), capturedBody...)),
unlock r.mu, run s.BodyFilter(bodyCopy) for the candidate, then re-lock r.mu and
re-check s.matched and URL/Method conditions before claiming the stub and
updating s.matched, s.CapturedHeaders, s.CapturedBody and s.CapturedBodies; this
prevents deadlocks from Register/Verify/RoundTrip re-entry and avoids letting
filters mutate the shared slice.
🧹 Nitpick comments (1)
shortcuts/contact/contact_search_user_fanout.go (1)

86-100: Minor: variable shadowing of body.

Line 88 declares a new body (string) that shadows the outer body (*searchUserAPIRequest) from line 73. While not a bug—the inner variable is only used for error message construction—renaming to respBody or errBody would improve clarity.

♻️ Suggested rename
 	if apiResp.StatusCode != http.StatusOK {
-		body := strings.TrimSpace(string(apiResp.RawBody))
+		respBody := strings.TrimSpace(string(apiResp.RawBody))
 		const maxBody = 200
-		if len(body) > maxBody {
-			body = body[:maxBody] + "..."
+		if len(respBody) > maxBody {
+			respBody = respBody[:maxBody] + "..."
 		}
 		msg := fmt.Sprintf("HTTP %d %s", apiResp.StatusCode, http.StatusText(apiResp.StatusCode))
-		if body != "" {
-			msg = fmt.Sprintf("%s: %s", msg, body)
+		if respBody != "" {
+			msg = fmt.Sprintf("%s: %s", msg, respBody)
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/contact/contact_search_user_fanout.go` around lines 86 - 100, The
inner variable named "body" that’s created when handling non-200 responses
shadows the outer "body" (*searchUserAPIRequest) and should be renamed for
clarity; update the error-handling block that reads apiResp.RawBody to use a
distinct name such as "respBody" or "errBody", adjust the subsequent length
check and msg construction to reference that new name, and return the same
fanoutResult (Index, Query, ErrMsg, ErrCode) unchanged—this removes the
shadowing while preserving the existing logic around apiResp and fanoutResult.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@cmd/config/init.go`:
- Around line 270-288: Add a focused unit/regression test that exercises the
Mode 3 path when opts.New is true and a --brand value is provided: stub or mock
runCreateAppFlow and assert it is invoked with the parsed brand from
parseBrand(opts.Brand) (e.g., "lark") rather than a fallback value; the test
should invoke the same call site logic as in init.go (the branch that calls
runCreateAppFlow(opts.Ctx, f, parseBrand(opts.Brand), msg)) and verify the brand
argument passed into the stubbed runCreateAppFlow, and also assert the rest of
the flow returns/handles the stubbed result appropriately (so saveInitConfig and
output.PrintJson behavior can be either no-ops or verified via mocks).

In `@internal/httpmock/registry.go`:
- Around line 101-125: The BodyFilter is invoked while holding r.mu and given
the shared capturedBody slice; change match in Registry so BodyFilter runs
outside the critical section on a defensive copy: while locked, iterate r.stubs
and skip by Method/URL and already matched (s.matched) to shortlist candidate
stub pointers (or simply note the index), then make a copy of the body (bodyCopy
:= append([]byte(nil), capturedBody...)), unlock r.mu, run
s.BodyFilter(bodyCopy) for the candidate, then re-lock r.mu and re-check
s.matched and URL/Method conditions before claiming the stub and updating
s.matched, s.CapturedHeaders, s.CapturedBody and s.CapturedBodies; this prevents
deadlocks from Register/Verify/RoundTrip re-entry and avoids letting filters
mutate the shared slice.

---

Nitpick comments:
In `@shortcuts/contact/contact_search_user_fanout.go`:
- Around line 86-100: The inner variable named "body" that’s created when
handling non-200 responses shadows the outer "body" (*searchUserAPIRequest) and
should be renamed for clarity; update the error-handling block that reads
apiResp.RawBody to use a distinct name such as "respBody" or "errBody", adjust
the subsequent length check and msg construction to reference that new name, and
return the same fanoutResult (Index, Query, ErrMsg, ErrCode) unchanged—this
removes the shadowing while preserving the existing logic around apiResp and
fanoutResult.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b27d469-0642-4ef1-a797-8a301618cb5f

📥 Commits

Reviewing files that changed from the base of the PR and between dce2beb and 8abb059.

📒 Files selected for processing (7)
  • cmd/config/init.go
  • internal/httpmock/registry.go
  • internal/output/format.go
  • shortcuts/contact/contact_search_user.go
  • shortcuts/contact/contact_search_user_fanout.go
  • shortcuts/contact/contact_search_user_test.go
  • skills/lark-contact/references/lark-contact-search-user.md

…-mode

Change-Id: I7e288cb5dca52d9c29a1002c35b2bc2ab43d3cf4
@github-actions github-actions Bot removed the domain/contact PR touches the contact domain label Apr 29, 2026
@liangshuo-1
liangshuo-1 merged commit 7752afa into main Apr 29, 2026
19 of 20 checks passed
@liangshuo-1
liangshuo-1 deleted the fix/brand-flag-in-new-mode branch April 29, 2026 09:13
HomyeeKing pushed a commit to HomyeeKing/cli that referenced this pull request May 6, 2026
* feat(contact +search-user): add --queries multi-name fanout

Add --queries CSV flag to lark-cli contact +search-user for parallel
multi-name fanout (up to 20 entries, partial-failure tolerant).

Output shape in fanout mode:
- data.users[] rows carry matched_query (string)
- data.queries[] sidecar lists each input with {query, error?, has_more}
- top-level data.has_more removed (per-query in queries[])
- error is omitempty; absent on success

Single --query mode is byte-for-byte unchanged (regression-guarded).
--queries is mutually exclusive with --query and --user-ids; bool
filters propagate to every sub-request.

Workers run with WaitGroup + buffered semaphore + index-slot writes;
each has defer recover() converting panics to internal error: ... in
the sidecar (no stack to stderr). Pre-canceled context returns
context canceled without making the request.

All-failed exit propagates first failure's HTTP/API code via ErrAPI;
falls back to ExitInternal for transport/parse/panic/ctx-canceled
(avoids emitting code 0, which means success in the Lark protocol).
HTTP non-200 ErrMsg now includes truncated response body for diagnosis.

Drive-by: signature field is now omitempty (mostly empty in practice).

Infrastructure:
- internal/httpmock gains BodyFilter/OnMatch/Reusable/CapturedBodies
  hooks to support concurrent stub-driven tests
- internal/output adds 'users' to knownArrayFields so CSV picks the
  primary array correctly

Change-Id: I3c14195fb8e094ae150002d90c36a0e4a0cc97d0

* fix(config/init): use parseBrand(opts.Brand) instead of hardcoded BrandFeishu in --new mode

The --new flag was ignoring the --brand flag and always passing BrandFeishu
to runCreateAppFlow. Now it correctly uses parseBrand(opts.Brand) to
respect the user's --brand parameter (e.g., --brand lark for international).

Change-Id: I1d4d78b3d586142b0210e6ceaeeb467b14e9c1a1
tuxedomm pushed a commit to zhumiaoxin/cli that referenced this pull request Jun 6, 2026
* feat(contact +search-user): add --queries multi-name fanout

Add --queries CSV flag to lark-cli contact +search-user for parallel
multi-name fanout (up to 20 entries, partial-failure tolerant).

Output shape in fanout mode:
- data.users[] rows carry matched_query (string)
- data.queries[] sidecar lists each input with {query, error?, has_more}
- top-level data.has_more removed (per-query in queries[])
- error is omitempty; absent on success

Single --query mode is byte-for-byte unchanged (regression-guarded).
--queries is mutually exclusive with --query and --user-ids; bool
filters propagate to every sub-request.

Workers run with WaitGroup + buffered semaphore + index-slot writes;
each has defer recover() converting panics to internal error: ... in
the sidecar (no stack to stderr). Pre-canceled context returns
context canceled without making the request.

All-failed exit propagates first failure's HTTP/API code via ErrAPI;
falls back to ExitInternal for transport/parse/panic/ctx-canceled
(avoids emitting code 0, which means success in the Lark protocol).
HTTP non-200 ErrMsg now includes truncated response body for diagnosis.

Drive-by: signature field is now omitempty (mostly empty in practice).

Infrastructure:
- internal/httpmock gains BodyFilter/OnMatch/Reusable/CapturedBodies
  hooks to support concurrent stub-driven tests
- internal/output adds 'users' to knownArrayFields so CSV picks the
  primary array correctly

Change-Id: I3c14195fb8e094ae150002d90c36a0e4a0cc97d0

* fix(config/init): use parseBrand(opts.Brand) instead of hardcoded BrandFeishu in --new mode

The --new flag was ignoring the --brand flag and always passing BrandFeishu
to runCreateAppFlow. Now it correctly uses parseBrand(opts.Brand) to
respect the user's --brand parameter (e.g., --brand lark for international).

Change-Id: I1d4d78b3d586142b0210e6ceaeeb467b14e9c1a1
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