Skip to content

CLI-248 git hooks#160

Merged
kirill-knize-sonarsource merged 2 commits intotask/kk/CLI-244-245-callback-infrastructurefrom
task/kk/CLI-248-git-hooks
Apr 15, 2026
Merged

CLI-248 git hooks#160
kirill-knize-sonarsource merged 2 commits intotask/kk/CLI-244-245-callback-infrastructurefrom
task/kk/CLI-248-git-hooks

Conversation

@kirill-knize-sonarsource
Copy link
Copy Markdown
Member

No description provided.

@hashicorp-vault-sonar-prod hashicorp-vault-sonar-prod Bot changed the title Task/kk/cli 248 git hooks CLI-280 Task/kk/cli 248 git hooks Apr 8, 2026
@sonar-review-alpha
Copy link
Copy Markdown
Contributor

sonar-review-alpha Bot commented Apr 8, 2026

Summary

This PR migrates git pre-commit hook logic from shell scripts into a new TypeScript handler (sonar hook git-pre-commit).

What changed: The shell scripts (getPreCommitHookScript(), getHuskyPreCommitSnippet()) are now thin wrappers that delegate to gitPreCommit(), which handles staged file detection, authentication checks, and secret scanning. The old inline shell logic (git diff parsing, file filtering, binary invocation) is removed.

Why: Decouples shell scripting from business logic. Makes the code type-safe, testable via unit tests with mocks, and easier to maintain. Graceful degradation (skipping when auth/binary unavailable) is clearer in TypeScript.

Test changes: Added comprehensive unit tests for gitPreCommit() with mocked dependencies, new integration tests using real git repos, and a test helper module. Restructured claude-pre-tool-use tests to front-load graceful skip scenarios. Added hook migration scenario tests (fresh install, old→new conversion, idempotency, hook preservation).

What reviewers should know

Start here:

  • Read src/cli/commands/hook/git-pre-commit.ts first — it's the core handler. Note the guard clause ordering (no files → auth → binary → scan).
  • Compare git-shell-fragments.ts before/after — shows how thin the wrapper scripts became.

Key decisions:

  • Guard order matters: Staged files checked first (cheapest), auth/binary checked before calling runSecretsBinary(). This keeps the hook fast in common cases.
  • Graceful skips: All guards return silently instead of erroring. The hook should never break the user's commit flow; it should only block if a secret is actually found.
  • Test structure shift: Skip scenarios now tested first with simple assertions (absence of "deny"), then happy path with detailed checks. This reflects real-world priority (preventing false positives matters more than detailed error reporting).

Non-obvious details:

  • getStagedFiles() catches all errors and returns [] — protects against missing git binary, missing repo, etc.
  • resolveSecretsBinaryPath() is synchronous, not async — binary location is resolved at startup.
  • Integration tests use Bun.which('git') to avoid PATH-based git lookup (security check S4036).
  • Hook migration scenarios test that old embedded-logic scripts are replaced, and that re-running integrate is idempotent.

Gotchas:

  • Pre-commit now exits 1 if secrets found, blocking the commit. Pre-push wasn't touched; verify pre-push logic if you change it.
  • "$SONAR_BIN" hook git-pre-commit in shell scripts assumes $SONAR_BIN is set by the bin block (check nativeBinBlock() and huskyBinBlock()).

  • Generate Walkthrough
  • Generate Diagram

🗣️ Give feedback

@hashicorp-vault-sonar-prod
Copy link
Copy Markdown

hashicorp-vault-sonar-prod Bot commented Apr 8, 2026

CLI-280

sonar-review-alpha[bot]

This comment was marked as resolved.

@kirill-knize-sonarsource kirill-knize-sonarsource force-pushed the task/kk/CLI-244-245-callback-infrastructure branch 2 times, most recently from e5bc9f8 to 6f2f053 Compare April 8, 2026 19:17
sonar-review-alpha[bot]

This comment was marked as outdated.

@kirill-knize-sonarsource kirill-knize-sonarsource force-pushed the task/kk/CLI-244-245-callback-infrastructure branch from 6f2f053 to aa6bc64 Compare April 8, 2026 19:58
@kirill-knize-sonarsource kirill-knize-sonarsource changed the title CLI-280 Task/kk/cli 248 git hooks CLI-280 git hooks Apr 8, 2026
@kirill-knize-sonarsource kirill-knize-sonarsource changed the title CLI-280 git hooks CLI-248 git hooks Apr 8, 2026
@kirill-knize-sonarsource kirill-knize-sonarsource changed the base branch from task/kk/CLI-244-245-callback-infrastructure to task/kk/CLI-247-post-tool-use April 8, 2026 20:05
sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as resolved.

@kirill-knize-sonarsource kirill-knize-sonarsource force-pushed the task/kk/CLI-247-post-tool-use branch 2 times, most recently from e2211ff to 581f8af Compare April 8, 2026 21:02
@kirill-knize-sonarsource kirill-knize-sonarsource force-pushed the task/kk/CLI-248-git-hooks branch 2 times, most recently from dc076f4 to f266954 Compare April 8, 2026 21:34
@kirill-knize-sonarsource kirill-knize-sonarsource force-pushed the task/kk/CLI-247-post-tool-use branch from 581f8af to 00bea22 Compare April 8, 2026 21:34
@kirill-knize-sonarsource kirill-knize-sonarsource changed the base branch from task/kk/CLI-247-post-tool-use to task/kk/CLI-244-245-callback-infrastructure April 8, 2026 21:39
Copy link
Copy Markdown
Contributor

@sonar-review-alpha sonar-review-alpha Bot left a comment

Choose a reason for hiding this comment

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

Conclusion: Solid PR — the migration from inline shell logic to TypeScript callback handlers is clean and well-tested. One logic duplication worth tidying up before merge.

🗣️ Give feedback

Comment on lines +40 to +49
try {
const exitCode = await scanFiles(binaryPath, stagedFiles, auth);
if (exitCode === EXIT_CODE_SECRETS_FOUND) {
throw new CommandFailedError('Secrets detected in staged files');
}
} catch (err) {
if (err instanceof CommandFailedError) throw err;
logger.debug(`git pre-commit secrets scan failed: ${(err as Error).message}`);
throw new CommandFailedError('Secrets scan failed');
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Logic duplication: This try-catch block — scanFiles → check EXIT_CODE_SECRETS_FOUND → re-throw as CommandFailedError, with a catch that re-throws CommandFailedError and wraps everything else — is duplicated verbatim in git-pre-push.ts lines 52–61. The only differences are the error message strings ('staged files' vs 'pushed commits') and the debug log prefix.

If the exit-code check or error-wrapping logic ever needs to change (e.g. new exit codes, changed error format, added telemetry), both files must be updated in sync.

Extract a shared helper into secrets-scan.ts (or a new run-secrets.ts), e.g.:

export async function runSecretsOrFail(
  binaryPath: string,
  files: string[],
  auth: ResolvedAuth,
  context: string,
): Promise<void> {
  try {
    const exitCode = await scanFiles(binaryPath, files, auth);
    if (exitCode === EXIT_CODE_SECRETS_FOUND) {
      throw new CommandFailedError(`Secrets detected in ${context}`);
    }
  } catch (err) {
    if (err instanceof CommandFailedError) throw err;
    logger.debug(`${context} secrets scan failed: ${(err as Error).message}`);
    throw new CommandFailedError('Secrets scan failed');
  }
}

Then call await runSecretsOrFail(binaryPath, stagedFiles, auth, 'staged files') and await runSecretsOrFail(binaryPath, files, auth, 'pushed commits') respectively.

  • Mark as noise

@kirill-knize-sonarsource kirill-knize-sonarsource force-pushed the task/kk/CLI-248-git-hooks branch 2 times, most recently from 39242f7 to 4ef81c1 Compare April 8, 2026 22:36
sonar-review-alpha[bot]

This comment was marked as outdated.

@kirill-knize-sonarsource kirill-knize-sonarsource force-pushed the task/kk/CLI-244-245-callback-infrastructure branch from c4b4665 to e44c9d3 Compare April 8, 2026 23:31
@kirill-knize-sonarsource kirill-knize-sonarsource force-pushed the task/kk/CLI-244-245-callback-infrastructure branch 2 times, most recently from 9191141 to 129cc50 Compare April 13, 2026 09:34
@kirill-knize-sonarsource kirill-knize-sonarsource force-pushed the task/kk/CLI-248-git-hooks branch 2 times, most recently from a7e0a09 to 384bf12 Compare April 13, 2026 12:20
sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as outdated.

Replaces the embedded shell logic in the pre-commit hook script with a
thin launcher that delegates to `sonar hook git-pre-commit`. The TypeScript
handler resolves auth and binary path, lists staged files via git, and calls
the secrets binary directly.
sonar-review-alpha[bot]

This comment was marked as outdated.

Comment thread src/cli/commands/hook/git-pre-commit.ts Outdated
const result = await runSecretsBinary(binaryPath, stagedFiles, auth);
const exitCode = result.exitCode ?? 1;
if (exitCode === EXIT_CODE_SECRETS_FOUND) {
throw new CommandFailedError('Secrets detected in staged files');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As a user I only see this generic message, maybe we could return more comprehensive error message that includes what caused the failure

const result = await harness.run('');
expect(result.exitCode).toBe(0);
expect(result.stdout).not.toContain('callback');
// 'hook' must not appear as a listed command entry (indented under COMMANDS)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe comment could be slightly more descriptive about why the regex pattern matters

Copy link
Copy Markdown
Contributor

@eray-felek-sonarsource eray-felek-sonarsource left a comment

Choose a reason for hiding this comment

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

Small comments, otherwise tested and LGTM

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@sonar-review-alpha sonar-review-alpha Bot left a comment

Choose a reason for hiding this comment

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

LGTM! ✅

The previously flagged logic duplication between git-pre-commit.ts and git-pre-push.ts remains open, but git-pre-push.ts does not exist yet in this codebase — so there is no actual current duplication to fix. The concern is valid for when that handler is eventually added.

🗣️ Give feedback

@kirill-knize-sonarsource kirill-knize-sonarsource merged commit d7cffa2 into task/kk/CLI-244-245-callback-infrastructure Apr 15, 2026
12 checks passed
@kirill-knize-sonarsource kirill-knize-sonarsource deleted the task/kk/CLI-248-git-hooks branch April 15, 2026 13:19
kirill-knize-sonarsource added a commit that referenced this pull request Apr 16, 2026
kirill-knize-sonarsource added a commit that referenced this pull request Apr 16, 2026
* CLI-244 sonar callback — command infrastructure

* CLI-245 PreToolUse secrets scanner callback

* CLI-246 Move prompt submit hook from shell scripts (#158)

* CLI-247 PostToolUse SQAA analysis callback (#159)

* CLI-248 git hooks (#160)

* CLI-298 git pre push (#175)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants