Skip to content

fix: isolate git credential helper chain for scoped repos - #2188

Merged
linchao5102 merged 1 commit into
mainfrom
fix/apps-git-credential-helper-chain
Aug 6, 2026
Merged

fix: isolate git credential helper chain for scoped repos#2188
linchao5102 merged 1 commit into
mainfrom
fix/apps-git-credential-helper-chain

Conversation

@linchao5102

@linchao5102 linchao5102 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

apps +git-credential-init previously configured a single URL-scoped Git
credential helper, which left other global helpers participating in
get/store/erase for the repo. This isolates the credential helper chain
so only lark-cli handles credentials for the configured repository URL.

Changes

  • Write the URL-scoped helper using Git's empty-helper reset semantics (reset
    the helper list, then add the lark-cli helper) together with
    useHttpPath=true, so earlier global helpers no longer participate for the
    repo URL.
  • Verify ownership of the existing URL-scoped configuration before writing;
    third-party, mixed, cross-origin, or include-sourced values fail closed
    without modifying user config.
  • Re-read the configuration after writing and roll back to the original values
    on a mid-write failure (unless an external change is detected).
  • Validate and shell-quote the app id, and normalize config origins so a
    relative GIT_CONFIG_GLOBAL still matches.
  • PAT issuance, SecretStore, and CLI output are unchanged.

Review-driven correctness fixes

Three gaps were found in review and fixed in this PR:

  1. Later-parsing helpers were not isolated. Git's empty-helper ("") reset
    only clears helpers that parse before the lark-cli section; a generic
    credential.helper (or one from a later [include]) that parses after it
    still participated in get/store/erase. SetHelper now verifies, using
    the faithful parse-order oracle (git config --includes --show-origin -z --list), that the lark-cli helper is last in fill order. If it is not, the
    section is repositioned to the end of the writable global file; if a
    competing helper lives in a file we must not edit, SetHelper fails closed
    with a FailedPrecondition error and restores the prior state — it never
    leaves a written-but-ineffective config.
  2. Concurrent read-modify-write could silently lose an --add. The
    optimistic read followed by an unconditional --unset-all could drop a
    concurrent write. SetHelper/UnsetHelper now serialize the
    read-modify-write of the writable global config file under a new
    cross-process lockGlobalConfig (ordered after lockApp, before lockURL).
  3. Teardown could leave an unrecoverable or silently-wrong state.
    UnsetHelper now deletes useHttpPath before the helper list, so a
    mid-teardown failure leaves a lark-cli-recoverable residue rather than a
    useHttpPath-only orphan that would block re-init. For a helper list that
    mixes lark-cli and third-party values, it removes only the lark-cli values
    and reports (non-fatally) that a third-party helper remains, instead of
    silently no-op'ing. The managed-state taxonomy gains Foreign/Partial/
    Mixed to drive these paths.

Residual / known limitation

lockGlobalConfig serializes concurrent lark-cli writers on the same global
config file. It does not serialize non-lark-cli writers (e.g. a user running
git config concurrently); such a race is detected by the post-write readback,
which fails closed with FailedPrecondition rather than silently clobbering the
foreign change.

Test Plan

  • make unit-test
  • go vet ./..., gofmt, go mod tidy (no changes)
  • go test ./shortcuts/apps/gitcred/... -race
  • Added unit tests covering ownership classification, reset semantics,
    rollback, cross-origin/include-source refusal, later-helper isolation and
    fail-closed, concurrent-writer serialization, recoverable teardown,
    mixed-owned cleanup, real git credential fill, and app-id shell-quoting
  • Sandbox end-to-end tests for the init flow and helper-chain reset
  • Manual verification of the URL-scoped config output

Related Issues

N/A

Summary by CodeRabbit

  • Bug Fixes

    • Improved Git credential helper management to prevent interference with unrelated configurations.
    • Added safer validation, verification, and rollback when Git settings change.
    • Improved cleanup of outdated credential settings when credentials are changed or removed.
    • Added support for recognizing and migrating previously managed Git configurations.
    • Scoped credential cleanup to the appropriate application.
    • Improved handling of concurrent configuration changes, shared configuration files, and cleanup failures.
  • Tests

    • Expanded coverage for Git configuration parsing, isolation, ownership, migration, ordering, failures, rollback, concurrency, and credential execution.

@linchao5102 linchao5102 added the bugfix Bug fixes label Aug 5, 2026
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

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

Changes

Git credential configuration now tracks origins and values, classifies managed state, validates ownership, verifies updates, and rolls back failures. UnsetHelper now requires appID. Credential lifecycle cleanup and tests use app-scoped removal.

Git credential configuration

Layer / File(s) Summary
Configuration state and origin discovery
shortcuts/apps/gitcred/gitconfig.go, shortcuts/apps/gitcred/gitconfig_test.go
Git configuration parsing, origin normalization, writable-origin discovery, managed-state classification, helper command generation, and external-command error handling were added with supporting tests.
Transactional helper mutation
shortcuts/apps/gitcred/gitconfig.go, shortcuts/apps/gitcred/lock.go, shortcuts/apps/gitcred/gitconfig_test.go
SetHelper and UnsetHelper now enforce ownership, update tracked values, control helper ordering, verify readback, and restore prior state after failures. Global configuration writes use path-keyed locks.
App-scoped cleanup integration
shortcuts/apps/gitcred/helper.go, shortcuts/apps/gitcred/gitcred_test.go, shortcuts/apps/git_credential_test.go
Credential initialization and deletion pass appID to UnsetHelper. Tests verify app-scoped cleanup, isolated real-Git execution, and targeted failure handling.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: raistlin042

Sequence Diagram(s)

sequenceDiagram
  participant CredentialHelper
  participant GitConfig
  participant GitExecutable
  CredentialHelper->>GitConfig: call SetHelper or UnsetHelper with gitHTTPURL and appID
  GitConfig->>GitExecutable: read origin-tagged helper and useHttpPath values
  GitExecutable-->>GitConfig: return configuration state
  GitConfig->>GitExecutable: update or remove managed configuration
  GitConfig->>GitExecutable: verify readback or restore prior state
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.28% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: isolating the Git credential helper chain for scoped repositories.
Description check ✅ Passed The description includes all required sections and provides clear scope, changes, testing details, and issue information.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/apps-git-credential-helper-chain

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

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

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label Aug 5, 2026
@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.03093% with 93 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.02%. Comparing base (ebdeda8) to head (8a76756).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
shortcuts/apps/gitcred/gitconfig.go 75.27% 49 Missing and 42 partials ⚠️
shortcuts/apps/gitcred/lock.go 88.88% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2188      +/-   ##
==========================================
+ Coverage   75.85%   76.02%   +0.16%     
==========================================
  Files         958      966       +8     
  Lines      101701   103017    +1316     
==========================================
+ Hits        77150    78319    +1169     
- Misses      18684    18746      +62     
- Partials     5867     5952      +85     

☔ View full report in Codecov by Harness.
📢 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 Aug 5, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

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

🧩 Skill update

npx skills add larksuite/cli#fix/apps-git-credential-helper-chain -y -g

@linchao5102
linchao5102 force-pushed the fix/apps-git-credential-helper-chain branch from b65948a to e8dd45d Compare August 5, 2026 03:09

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
shortcuts/apps/gitcred/gitconfig.go (1)

70-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider removing the immediate second read.

Lines 63 and 70 read the configuration twice with no write between them. The comparison at line 74 therefore cannot detect a concurrent change that the readback at line 91 would miss. It only adds two more git config subprocesses per SetHelper call.

If the intent is to detect external changes, the readback at lines 87-93 already provides that. If the intent is different, add a comment that states it.

♻️ Proposed simplification
 	if classifyManagedState(snapshot, canonical) == managedNone {
 		return gitConfigNotOwnedError(normalizedURL)
 	}
-	current, err := readCredentialConfig(ctx, normalizedURL)
-	if err != nil {
-		return err
-	}
-	if !reflect.DeepEqual(current, snapshot) {
-		return gitConfigChangedError(normalizedURL)
-	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/apps/gitcred/gitconfig.go` around lines 70 - 76, Remove the
redundant immediate read and comparison around readCredentialConfig in
SetHelper, including the current/snapshot DeepEqual check and
gitConfigChangedError return. Retain the later readback comparison after the
configuration write as the sole external-change check.
shortcuts/apps/git_credential_test.go (1)

1234-1258: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared fake-git setup.

installAppsFakeGitUnsetUseHTTPPathFailure repeats every step of installAppsFakeGit: exec.LookPath("git"), the temp bin directory, the script write, the isolated global config, and the three t.Setenv calls. Only the shell case pattern differs. A change to the isolation setup now has to be applied in two places.

♻️ Proposed refactor
+// installAppsFakeGitWithCase installs a git wrapper on PATH that delegates to
+// the real git, except for argv matching failCase, and isolates global config.
+func installAppsFakeGitWithCase(t *testing.T, failCase string) {
+	t.Helper()
+	realGit, err := exec.LookPath("git")
+	if err != nil {
+		t.Fatalf("find real git: %v", err)
+	}
+	dir := t.TempDir()
+	script := fmt.Sprintf("#!/bin/sh\n%sexec %q \"$@\"\n", failCase, realGit)
+	if err := os.WriteFile(filepath.Join(dir, "git"), []byte(script), 0o700); err != nil {
+		t.Fatalf("write fake git: %v", err)
+	}
+	globalConfig := filepath.Join(t.TempDir(), "global.config")
+	if err := os.WriteFile(globalConfig, nil, 0o600); err != nil {
+		t.Fatalf("write isolated global git config: %v", err)
+	}
+	t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
+	t.Setenv("GIT_CONFIG_GLOBAL", globalConfig)
+	t.Setenv("GIT_CONFIG_NOSYSTEM", "1")
+}
+
+func installAppsFakeGit(t *testing.T, failUseHTTPPathExit int) {
+	t.Helper()
+	failCase := ""
+	if failUseHTTPPathExit != 0 {
+		failCase = fmt.Sprintf("case \"$*\" in\n  *\"useHttpPath true\"*) exit %d ;;\nesac\n", failUseHTTPPathExit)
+	}
+	installAppsFakeGitWithCase(t, failCase)
+}
+
+func installAppsFakeGitUnsetUseHTTPPathFailure(t *testing.T, exitCode int) {
+	t.Helper()
+	installAppsFakeGitWithCase(t, fmt.Sprintf("case \"$*\" in\n  *\"--unset-all\"*\"useHttpPath\"*) exit %d ;;\nesac\n", exitCode))
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/apps/git_credential_test.go` around lines 1234 - 1258, Extract the
shared fake-git setup from installAppsFakeGitUnsetUseHTTPPathFailure and
installAppsFakeGit into a reusable helper that handles git lookup, temporary
script installation, isolated global config creation, and all PATH/GIT_CONFIG
environment setup. Keep the failure helper’s distinct shell case pattern and
exit-code behavior, while having both helpers reuse the common setup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shortcuts/apps/gitcred/gitconfig_test.go`:
- Around line 208-214: Update the malformed-output test cases around
parseOriginValues to retain the returned error and validate it with the existing
assertProblemSubtype helper, asserting the InternalError metadata includes
errs.SubtypeExternalTool. Also verify the underlying cause is preserved through
errs.ProblemOf, while keeping the existing non-nil error assertion behavior.

---

Nitpick comments:
In `@shortcuts/apps/git_credential_test.go`:
- Around line 1234-1258: Extract the shared fake-git setup from
installAppsFakeGitUnsetUseHTTPPathFailure and installAppsFakeGit into a reusable
helper that handles git lookup, temporary script installation, isolated global
config creation, and all PATH/GIT_CONFIG environment setup. Keep the failure
helper’s distinct shell case pattern and exit-code behavior, while having both
helpers reuse the common setup.

In `@shortcuts/apps/gitcred/gitconfig.go`:
- Around line 70-76: Remove the redundant immediate read and comparison around
readCredentialConfig in SetHelper, including the current/snapshot DeepEqual
check and gitConfigChangedError return. Retain the later readback comparison
after the configuration write as the sole external-change check.
🪄 Autofix

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 Plus

Run ID: 6f74e3dd-d6ba-4a2b-a4b9-1e81a11309d6

📥 Commits

Reviewing files that changed from the base of the PR and between ebdeda8 and b65948a.

📒 Files selected for processing (5)
  • shortcuts/apps/git_credential_test.go
  • shortcuts/apps/gitcred/gitconfig.go
  • shortcuts/apps/gitcred/gitconfig_test.go
  • shortcuts/apps/gitcred/gitcred_test.go
  • shortcuts/apps/gitcred/helper.go

Comment thread shortcuts/apps/gitcred/gitconfig_test.go
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@linchao5102
linchao5102 force-pushed the fix/apps-git-credential-helper-chain branch from e8dd45d to 64206ac Compare August 5, 2026 03:27
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@linchao5102

Copy link
Copy Markdown
Collaborator Author

Good catch — done in 64206ac. TestParseOriginValuesRejectsMalformedOutput now asserts the typed subtype via assertProblemSubtype(t, err, errs.SubtypeExternalTool) instead of only checking for a non-nil error, matching the error-path testing guideline.

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
shortcuts/apps/gitcred/gitconfig_test.go (1)

788-797: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the problem category in assertProblemSubtype.

The helper checks only problem.Subtype. Every error-path test in this file routes through it, so no test pins the error category. A change that kept the subtype but moved the error to a different category would still pass.

Add the expected category as a parameter, or assert it inline where the category is fixed for the call site.

♻️ Proposed refactor
-func assertProblemSubtype(t *testing.T, err error, subtype errs.Subtype) {
+func assertProblem(t *testing.T, err error, category errs.Category, subtype errs.Subtype) {
 	t.Helper()
 	if err == nil {
-		t.Fatalf("error = nil, want subtype %s", subtype)
+		t.Fatalf("error = nil, want category %s subtype %s", category, subtype)
 	}
 	problem, ok := errs.ProblemOf(err)
-	if !ok || problem.Subtype != subtype {
-		t.Fatalf("problem = %#v, ok = %v, want subtype %s", problem, ok, subtype)
+	if !ok || problem.Category != category || problem.Subtype != subtype {
+		t.Fatalf("problem = %#v, ok = %v, want category %s subtype %s", problem, ok, category, subtype)
 	}
 }

Param is not available on Problem, so do not assert it here. If a call site needs Param, use errors.As with *errs.ValidationError instead.

As per coding guidelines: "Error-path tests must assert typed metadata through errs.ProblemOf (category, subtype, and param)". Based on learnings: "errs.ProblemOf(err) returns (*errs.Problem, bool) and the returned Problem contains only problem-level fields ... it does not have Param."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/apps/gitcred/gitconfig_test.go` around lines 788 - 797, Update
assertProblemSubtype to accept the expected errs.Category and assert
problem.Category alongside problem.Subtype via errs.ProblemOf. Update every call
site to provide its expected category; do not assert Param on errs.Problem,
using errors.As with *errs.ValidationError only where a call site specifically
requires Param.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shortcuts/apps/gitcred/gitconfig_test.go`:
- Around line 395-396: Update the helper script contents in
TestGlobalGitConfigResetFirstHelperStopsGlobalFill to drain stdin with cat
>/dev/null before executing printf, for both globalHelper and scopedHelper.
Preserve their existing logging behavior and executable permissions so approve
and erase operations complete without broken-pipe failures.
- Around line 254-285: Update TestWritableGlobalOrigin to set USERPROFILE
alongside HOME for Windows variants, using the test home directory so
vfs.UserHomeDir() resolves consistently through os.UserHomeDir(). Keep the
existing environment setup and non-Windows fixtures unchanged.
- Around line 596-603: Update the nonCanonical path construction in the test to
build the “./global.config” suffix using string(filepath.Separator) instead of
hardcoded forward slashes, while preserving the intentionally non-canonical path
used for GIT_CONFIG_GLOBAL.

---

Nitpick comments:
In `@shortcuts/apps/gitcred/gitconfig_test.go`:
- Around line 788-797: Update assertProblemSubtype to accept the expected
errs.Category and assert problem.Category alongside problem.Subtype via
errs.ProblemOf. Update every call site to provide its expected category; do not
assert Param on errs.Problem, using errors.As with *errs.ValidationError only
where a call site specifically requires Param.
🪄 Autofix

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 Plus

Run ID: 29289f4f-917c-4274-9fcb-b514a0d5e5c4

📥 Commits

Reviewing files that changed from the base of the PR and between ebdeda8 and 64206ac.

📒 Files selected for processing (5)
  • shortcuts/apps/git_credential_test.go
  • shortcuts/apps/gitcred/gitconfig.go
  • shortcuts/apps/gitcred/gitconfig_test.go
  • shortcuts/apps/gitcred/gitcred_test.go
  • shortcuts/apps/gitcred/helper.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • shortcuts/apps/gitcred/gitcred_test.go
  • shortcuts/apps/gitcred/helper.go
  • shortcuts/apps/git_credential_test.go
  • shortcuts/apps/gitcred/gitconfig.go

Comment thread shortcuts/apps/gitcred/gitconfig_test.go
Comment thread shortcuts/apps/gitcred/gitconfig_test.go Outdated
Comment thread shortcuts/apps/gitcred/gitconfig_test.go
@linchao5102
linchao5102 force-pushed the fix/apps-git-credential-helper-chain branch from 64206ac to 97c69cd Compare August 5, 2026 03:50
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
shortcuts/apps/gitcred/gitconfig_test.go (1)

817-831: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the failure-injection wrapper fail loudly if it never matches.

The script matches the exact positional form git config --global <useHttpPath key> true. The three rollback tests depend on this match to inject exit code 23.

If gitConfigSet later changes its argument order or adds a flag such as --replace-all, the branch stops matching. The wrapper then forwards the call to the real git, SetHelper succeeds, and assertExternalToolExit reports a generic "want git exit code 23" failure that does not indicate the harness itself went stale. The --unset-all branch has the same coupling.

Record a marker file when the injection branch fires, then assert that the marker exists. The tests then distinguish "rollback behaviour regressed" from "the wrapper no longer intercepts the call".

♻️ Proposed refactor
 	script := `#!/bin/sh
 if [ "$1" = "config" ] && [ "$2" = "--global" ] && [ "$3" = "$GIT_TEST_USE_PATH_KEY" ] && [ "$4" = "true" ]; then
+  : > "$GIT_TEST_INJECTED_MARKER"
   if [ "$GIT_TEST_EXTERNAL_CHANGE" = "1" ]; then
     "$GIT_TEST_REAL_GIT" config --global --add "$GIT_TEST_HELPER_KEY" '!external-change'
   fi
   exit 23
 fi

Set GIT_TEST_INJECTED_MARKER in the helper, return its path, and check it in each rollback test:

markerPath := installGitConfigUseHTTPPathFailure(t, url, false, false)
// ... after SetHelper
if _, err := os.Stat(markerPath); err != nil {
	t.Fatalf("failure injection never fired; the git wrapper no longer matches the real command: %v", err)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/apps/gitcred/gitconfig_test.go` around lines 817 - 831, Update the
failure-injection helper around installGitConfigUseHTTPPathFailure to create and
expose a marker path via GIT_TEST_INJECTED_MARKER, and have the wrapper write
the marker when the exit-23 interception branch executes. Return that path to
each rollback test and assert it exists after SetHelper, so unmatched git
argument changes fail with an explicit harness error; apply equivalent coverage
to the --unset-all injection branch as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shortcuts/apps/gitcred/gitconfig_test.go`:
- Around line 476-482: The redaction assertion in the rollback error test checks
the already-validated static hint instead of the complete error chain. Keep the
exact rollbackFailureHint assertion, but change the sensitive-rollback-detail
check to inspect err.Error() so leaked stderr content in the error message
causes the test to fail.
- Around line 20-21: Update the test fixture helpers in gitconfig_test.go to use
standard filesystem APIs: replace vfs.MkdirAll and vfs.WriteFile in
writeTestFileMode with os.MkdirAll and os.WriteFile, and replace all remaining
test-only vfs.ReadFile calls with os.ReadFile while preserving the existing
fs.ErrNotExist handling.

---

Nitpick comments:
In `@shortcuts/apps/gitcred/gitconfig_test.go`:
- Around line 817-831: Update the failure-injection helper around
installGitConfigUseHTTPPathFailure to create and expose a marker path via
GIT_TEST_INJECTED_MARKER, and have the wrapper write the marker when the exit-23
interception branch executes. Return that path to each rollback test and assert
it exists after SetHelper, so unmatched git argument changes fail with an
explicit harness error; apply equivalent coverage to the --unset-all injection
branch as needed.
🪄 Autofix

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 Plus

Run ID: 2dd906f5-5b70-4347-b4b1-c0a5504cd9cd

📥 Commits

Reviewing files that changed from the base of the PR and between ebdeda8 and 97c69cd.

📒 Files selected for processing (5)
  • shortcuts/apps/git_credential_test.go
  • shortcuts/apps/gitcred/gitconfig.go
  • shortcuts/apps/gitcred/gitconfig_test.go
  • shortcuts/apps/gitcred/gitcred_test.go
  • shortcuts/apps/gitcred/helper.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • shortcuts/apps/gitcred/helper.go
  • shortcuts/apps/git_credential_test.go
  • shortcuts/apps/gitcred/gitconfig.go
  • shortcuts/apps/gitcred/gitcred_test.go

Comment thread shortcuts/apps/gitcred/gitconfig_test.go Outdated
Comment thread shortcuts/apps/gitcred/gitconfig_test.go
@linchao5102
linchao5102 force-pushed the fix/apps-git-credential-helper-chain branch from 97c69cd to 9b40210 Compare August 5, 2026 04:09
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

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

🧹 Nitpick comments (1)
shortcuts/apps/gitcred/gitconfig_test.go (1)

259-262: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Remove the redundant global assignment.

Lines 259-262 replace globalEnv with itself when non-empty; set global := tc.globalEnv and skip the conditional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/apps/gitcred/gitconfig_test.go` around lines 259 - 262, In the test
setup around tc.globalEnv, remove the conditional reassignment and initialize
global directly from tc.globalEnv. Preserve the existing value unchanged for
both empty and non-empty inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@shortcuts/apps/gitcred/gitconfig_test.go`:
- Around line 259-262: In the test setup around tc.globalEnv, remove the
conditional reassignment and initialize global directly from tc.globalEnv.
Preserve the existing value unchanged for both empty and non-empty inputs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0470350a-a93c-4b63-8a92-9ac7d9010542

📥 Commits

Reviewing files that changed from the base of the PR and between ebdeda8 and 9b40210.

📒 Files selected for processing (5)
  • shortcuts/apps/git_credential_test.go
  • shortcuts/apps/gitcred/gitconfig.go
  • shortcuts/apps/gitcred/gitconfig_test.go
  • shortcuts/apps/gitcred/gitcred_test.go
  • shortcuts/apps/gitcred/helper.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • shortcuts/apps/gitcred/helper.go
  • shortcuts/apps/git_credential_test.go
  • shortcuts/apps/gitcred/gitconfig.go
  • shortcuts/apps/gitcred/gitcred_test.go

@linchao5102
linchao5102 force-pushed the fix/apps-git-credential-helper-chain branch 2 times, most recently from bca8ce3 to cab0ee4 Compare August 5, 2026 14:34
@linchao5102

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shortcuts/apps/gitcred/gitconfig.go`:
- Around line 455-467: Update repositionLarkSection and its
SetHelper/rollbackIfUnchanged flow to preserve every key in the URL-scoped
credential section when repositioning, including keys not represented by Helpers
or UseHTTPPath; capture the full section before removal and restore untracked
entries alongside the canonical helper values. If faithful preservation cannot
be implemented, refuse repositioning with the repository’s typed validation
error rather than deleting unsupported keys.
- Around line 424-425: Update the scopedKey construction near genericKey so it
does not lowercase the full gitCredentialKey result: preserve the URL subsection
exactly as returned by gitCredentialKey while lowercasing only the section and
final key component as required. Add a regression test covering a URL with an
uppercase path segment and verify the helper update/revert flow succeeds without
triggering gitConfigHelperOrderUnsafeError.

In `@shortcuts/apps/gitcred/lock.go`:
- Around line 84-92: Update lockGlobalConfig to resolve the origin path to an
absolute form after removing the file: prefix and before applying
safeLockNameChars and constructing the lock filename, ensuring relative
GIT_CONFIG_GLOBAL values map to the same cross-process lock regardless of
working directory.
🪄 Autofix

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 Plus

Run ID: 3282f622-e0ac-409c-83a5-4edd6ac41fd1

📥 Commits

Reviewing files that changed from the base of the PR and between bdb1964 and cab0ee4.

📒 Files selected for processing (6)
  • shortcuts/apps/git_credential_test.go
  • shortcuts/apps/gitcred/gitconfig.go
  • shortcuts/apps/gitcred/gitconfig_test.go
  • shortcuts/apps/gitcred/gitcred_test.go
  • shortcuts/apps/gitcred/helper.go
  • shortcuts/apps/gitcred/lock.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • shortcuts/apps/gitcred/helper.go
  • shortcuts/apps/git_credential_test.go
  • shortcuts/apps/gitcred/gitcred_test.go

Comment thread shortcuts/apps/gitcred/gitconfig.go Outdated
Comment thread shortcuts/apps/gitcred/gitconfig.go
Comment thread shortcuts/apps/gitcred/lock.go
@linchao5102
linchao5102 force-pushed the fix/apps-git-credential-helper-chain branch 2 times, most recently from 554467a to ddbc98e Compare August 5, 2026 15:44
@linchao5102

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
shortcuts/apps/gitcred/helper.go (1)

147-147: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider falling back to the validated appID when the record has none.

Both call sites read the app ID from the stored record. UnsetHelper now calls validate.ResourceName(appID, "appID"), so an empty or stale record.AppID makes cleanup fail and only surfaces a ConfigWarning; the URL-scoped helper stays in the global Git config.

previous comes from currentAppRecord(appID) and records comes from FindByAppID(appID, ...), so the validated appID parameter is the same value for every record. Use it when the record field is empty.

♻️ Proposed change (Line 147)
-			if err := m.GitConfig.UnsetHelper(ctx, previous.GitHTTPURL, previous.AppID); err != nil {
+			previousAppID := previous.AppID
+			if previousAppID == "" {
+				previousAppID = appID
+			}
+			if err := m.GitConfig.UnsetHelper(ctx, previous.GitHTTPURL, previousAppID); err != nil {
 				result.ConfigWarning = err.Error()
 			}

Also applies to: 178-178

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/apps/gitcred/helper.go` at line 147, Update both UnsetHelper call
sites in the cleanup flow to use the validated appID parameter when
previous.AppID or the corresponding record AppID is empty, while preserving
non-empty stored IDs. Ensure cleanup passes this fallback consistently to
GitConfig.UnsetHelper so URL-scoped helpers are removed.
shortcuts/apps/git_credential_test.go (1)

1205-1232: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Guard the real-git wrappers for platform and git availability.

Both helpers now require the real git binary and a POSIX shell. Two gaps follow:

  1. exec.LookPath("git") failure calls t.Fatalf, so the whole test fails on an environment without git. The previous synthetic wrapper had no such dependency. Use t.Skip for a missing git, and skip on Windows, as gitconfig_test.go does for its shell wrappers.
  2. The case "$*" patterns depend on the exact argv join. installAppsFakeGit matches useHttpPath true, which holds only while the code calls git config --global <key> true. If the argument form changes, the wrapper stops injecting the failure and the test still passes while asserting the warning path. Match the discrete arguments instead, as installGitConfigUseHTTPPathFailure in gitconfig_test.go does.
♻️ Proposed guard
 func installAppsFakeGit(t *testing.T, failUseHTTPPathExit int) {
 	t.Helper()
+	if runtime.GOOS == "windows" {
+		t.Skip("test wrapper is a POSIX shell script")
+	}
 	realGit, err := exec.LookPath("git")
 	if err != nil {
-		t.Fatalf("find real git: %v", err)
+		t.Skipf("real git is not available: %v", err)
 	}

Also applies to: 1234-1257

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/apps/git_credential_test.go` around lines 1205 - 1232, Update
installAppsFakeGit and the related real-git wrapper helper to skip on Windows
and use t.Skip when exec.LookPath("git") cannot find git, matching the platform
guards in gitconfig_test.go. Replace the case "$*" substring matching for
useHttpPath true with discrete-argument matching equivalent to
installGitConfigUseHTTPPathFailure, so failure injection remains tied to the
intended arguments rather than their joined representation.
shortcuts/apps/gitcred/gitconfig.go (1)

95-101: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider dropping the duplicate ownership read.

Lines 88 and 95 call readCredentialConfig twice with no write between them, and both reads happen under lockGlobalConfig. The comparison at Line 99 can only detect an external write that lands between two adjacent reads. The readback at Line 135 already covers divergence after the writes. Each extra read costs two git config subprocesses per SetHelper call.

If the double read is intentional for a specific race, add a comment that names it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/apps/gitcred/gitconfig.go` around lines 95 - 101, Remove the second
readCredentialConfig call and its current-vs-snapshot comparison from the
SetHelper flow, since no write occurs between the reads and the later readback
already verifies post-write divergence. Preserve the initial
ownership/configuration read and existing error handling; only retain the
duplicate read if a documented race requires it, with a comment naming that
race.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shortcuts/apps/gitcred/gitconfig.go`:
- Around line 501-509: Update sectionHasUntrackedKeys to pass Git’s
--no-includes option to the global config --get-regexp command, ensuring the
scan only examines the editable global file and matches the scope affected by
--remove-section.

In `@shortcuts/apps/gitcred/lock.go`:
- Around line 84-91: Wrap the error returned by acquireFileLock in
lockGlobalConfig with errs.NewInternalError using errs.SubtypeStorage, a
descriptive lock-acquisition message, and WithCause(err). Preserve the existing
directory-creation handling and return the typed error so SetHelper and
UnsetHelper receive consistent metadata.

---

Nitpick comments:
In `@shortcuts/apps/git_credential_test.go`:
- Around line 1205-1232: Update installAppsFakeGit and the related real-git
wrapper helper to skip on Windows and use t.Skip when exec.LookPath("git")
cannot find git, matching the platform guards in gitconfig_test.go. Replace the
case "$*" substring matching for useHttpPath true with discrete-argument
matching equivalent to installGitConfigUseHTTPPathFailure, so failure injection
remains tied to the intended arguments rather than their joined representation.

In `@shortcuts/apps/gitcred/gitconfig.go`:
- Around line 95-101: Remove the second readCredentialConfig call and its
current-vs-snapshot comparison from the SetHelper flow, since no write occurs
between the reads and the later readback already verifies post-write divergence.
Preserve the initial ownership/configuration read and existing error handling;
only retain the duplicate read if a documented race requires it, with a comment
naming that race.

In `@shortcuts/apps/gitcred/helper.go`:
- Line 147: Update both UnsetHelper call sites in the cleanup flow to use the
validated appID parameter when previous.AppID or the corresponding record AppID
is empty, while preserving non-empty stored IDs. Ensure cleanup passes this
fallback consistently to GitConfig.UnsetHelper so URL-scoped helpers are
removed.
🪄 Autofix

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 Plus

Run ID: 5b7053ac-ce6f-4c99-8008-b8fbb5a31cbc

📥 Commits

Reviewing files that changed from the base of the PR and between 960bdf6 and ddbc98e.

📒 Files selected for processing (6)
  • shortcuts/apps/git_credential_test.go
  • shortcuts/apps/gitcred/gitconfig.go
  • shortcuts/apps/gitcred/gitconfig_test.go
  • shortcuts/apps/gitcred/gitcred_test.go
  • shortcuts/apps/gitcred/helper.go
  • shortcuts/apps/gitcred/lock.go

Comment thread shortcuts/apps/gitcred/gitconfig.go
Comment thread shortcuts/apps/gitcred/lock.go
Scoped repos created by Miaoda apps failed day-2 `git pull/push`
authentication because a stale, higher-priority credential helper (e.g.
macOS osxkeychain) served cached credentials instead of the lark-cli
helper. This isolates the URL-scoped credential helper chain and closes
correctness gaps found in review:

1. Helper-order isolation. Git's empty-helper ("") reset only clears
   helpers that parse BEFORE the lark-cli section; a generic
   credential.helper (or one from a later [include]) that parses AFTER it
   still participated in get/store/erase. SetHelper now verifies, via the
   faithful parse-order oracle (`git config --includes --show-origin -z
   --list`), that the lark-cli helper is last in fill order. If not, it
   repositions the section to the end of the writable file; if a later
   helper lives in a file we must not edit, it fails closed with a
   FailedPrecondition error and restores the prior state.

2. Concurrent read-modify-write. The read-modify-write of the writable
   global config file is serialized across lark-cli processes by a new
   cross-process lockGlobalConfig, ordered after lockApp and before
   lockURL. That lock cannot stop an unrelated (non-lark-cli) process, so
   the helper rewrite also no longer clears the whole key: it deletes only
   the values observed in the ownership snapshot by exact match, leaving a
   helper a third party inserted during the write window in place. The
   readback then diverges from the expected state and SetHelper fails
   closed with the foreign value preserved, instead of a whole-key
   --unset-all silently deleting it.

3. Recoverable teardown. UnsetHelper now deletes useHttpPath before the
   helper list so a mid-teardown failure leaves a lark-cli-recoverable
   residue (never a useHttpPath-only orphan that blocks re-init), and
   removes only lark-cli values from a mixed list, reporting that a
   third-party helper remains rather than silently no-op'ing. The state
   taxonomy gains Foreign/Partial/Mixed to drive these paths.
@linchao5102
linchao5102 force-pushed the fix/apps-git-credential-helper-chain branch from ddbc98e to 8a76756 Compare August 5, 2026 16:40
@linchao5102
linchao5102 merged commit 426f80f into main Aug 6, 2026
40 checks passed
@linchao5102
linchao5102 deleted the fix/apps-git-credential-helper-chain branch August 6, 2026 07:16
zkh-bytedance pushed a commit that referenced this pull request Aug 6, 2026
Scoped repos created by Miaoda apps failed day-2 `git pull/push`
authentication because a stale, higher-priority credential helper (e.g.
macOS osxkeychain) served cached credentials instead of the lark-cli
helper. This isolates the URL-scoped credential helper chain and closes
correctness gaps found in review:

1. Helper-order isolation. Git's empty-helper ("") reset only clears
   helpers that parse BEFORE the lark-cli section; a generic
   credential.helper (or one from a later [include]) that parses AFTER it
   still participated in get/store/erase. SetHelper now verifies, via the
   faithful parse-order oracle (`git config --includes --show-origin -z
   --list`), that the lark-cli helper is last in fill order. If not, it
   repositions the section to the end of the writable file; if a later
   helper lives in a file we must not edit, it fails closed with a
   FailedPrecondition error and restores the prior state.

2. Concurrent read-modify-write. The read-modify-write of the writable
   global config file is serialized across lark-cli processes by a new
   cross-process lockGlobalConfig, ordered after lockApp and before
   lockURL. That lock cannot stop an unrelated (non-lark-cli) process, so
   the helper rewrite also no longer clears the whole key: it deletes only
   the values observed in the ownership snapshot by exact match, leaving a
   helper a third party inserted during the write window in place. The
   readback then diverges from the expected state and SetHelper fails
   closed with the foreign value preserved, instead of a whole-key
   --unset-all silently deleting it.

3. Recoverable teardown. UnsetHelper now deletes useHttpPath before the
   helper list so a mid-teardown failure leaves a lark-cli-recoverable
   residue (never a useHttpPath-only orphan that blocks re-init), and
   removes only lark-cli values from a mixed list, reporting that a
   third-party helper remains rather than silently no-op'ing. The state
   taxonomy gains Foreign/Partial/Mixed to drive these paths.

Co-authored-by: TRAE CLI <traecli@bytedance.com>
@liangshuo-1 liangshuo-1 mentioned this pull request Aug 7, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Bug fixes 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