Skip to content

fix(hooks): collapse multi-line commands in bash audit logs#741

Merged
affaan-m merged 4 commits into
affaan-m:mainfrom
cskwork:fix/issue-734-multiline-command-logging
Mar 31, 2026
Merged

fix(hooks): collapse multi-line commands in bash audit logs#741
affaan-m merged 4 commits into
affaan-m:mainfrom
cskwork:fix/issue-734-multiline-command-logging

Conversation

@cskwork

@cskwork cskwork commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #734

Multi-line bash commands were producing multi-line log entries, breaking downstream line-based parsing tools (dashboards, frequency analysis).

Changes

  • Added gsub("\\n"; " ") to jq filters in both bash audit log and cost tracker hooks in hooks/hooks.json
  • Updated rules/hooks.md documentation to list the new hooks

Test plan

  • Multi-line commands now logged as single lines
  • Timestamps and formatting preserved
  • No impact on single-line command logging

Summary by cubic

Collapse multi-line Bash commands into single-line audit and cost-tracker logs, preserve stdin for downstream hooks, and harden secret redaction. Fixes #734.

  • Bug Fixes
    • Collapse newlines via jq gsub("\n"; " "); keep timestamps; robust Authorization redaction using [: ]* to handle Authorization: Bearer <token>; redact AKIA/ASIA, --token, password=, and GitHub tokens (ghp_, gho_, ghs_, github_pat_); log to ~/.claude/bash-commands.log and ~/.claude/cost-tracker.log.
    • Unconditional stdin passthrough: printf '%s\n' "$INPUT"; use ; so passthrough runs even if jq fails; add || true after jq.

Written for commit 0c00f26. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added bash command logging with timestamps for audit trail purposes
    • Introduced cost tracking for bash tool usage
  • Documentation

    • Updated documentation to describe new bash audit logging and cost tracking functionality

@coderabbitai

coderabbitai Bot commented Mar 22, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Two new PostToolUse hooks are added to log bash tool executions. The hooks write timestamped entries to ~/.claude/bash-commands.log and ~/.claude/cost-tracker.log, with multi-line commands collapsed to single lines via newline-to-space substitution using jq's gsub() function.

Changes

Cohort / File(s) Summary
Hook Configuration
hooks/hooks.json
Added two new PostToolUse hook entries for Bash tool that log commands to separate files in ~/.claude/ directory using jq with gsub("\\n"; " ") to collapse multi-line commands into single-line log entries.
Hook Documentation
rules/hooks.md
Updated documentation to describe the two new PostToolUse hooks: Bash audit log and Cost tracker.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

🐰 Logs hop along, timestamped and bright,
Multi-line commands now collapse just right,
Bash commands tracked in a single-line stream,
Cost tracking logs fulfill the dream! 🎯

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: collapsing multi-line commands in bash audit logs via the addition of gsub newline substitution.
Linked Issues check ✅ Passed The PR implements the exact fix specified in issue #734: adding gsub("\\n"; " ") to collapse multi-line commands into single log lines in both bash audit log and cost-tracker hooks.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing issue #734: adding gsub filters to hooks and documenting them in the rules file. No unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

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

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds two PostToolUse hooks for Bash in hooks/hooks.json: one audit log hook writing to ~/.claude/bash-commands.log and one cost-tracker hook writing to ~/.claude/cost-tracker.log. The core goal is to collapse multi-line bash commands into single-line log entries by applying a gsub for newline characters, while also redacting common secrets and forwarding stdin to downstream hooks.

What was improved relative to previous review feedback:

  • mkdir -p ~/.claude is now prepended so the log directory is created if absent.
  • || true after the jq invocation prevents a non-zero jq exit from breaking the hook chain.
  • printf '%s\n' "$INPUT" is used for the stdin passthrough instead of relying on a chained echo inside an && chain, so downstream hooks always receive the payload.
  • The Authorization redaction pattern was updated from [^ ]* to [: ]*[^ ]*[: ]*[^ ]*, correctly handling the Bearer <token> scheme-plus-value format.
  • The gsub newline pattern now uses a JSON \n literal (actual newline character) rather than the over-escaped \\\\n that matched only literal backslash-n pairs.

Remaining concerns:

  • Neither new hook has a "timeout" field, unlike other synchronous PostToolUse hooks in this file that set "timeout": 5 or run async: true. If jq stalls, the entire hook chain blocks indefinitely.
  • The full 10-pattern gsub chain is copy-pasted verbatim between both hooks; divergence over time is likely.
  • Credential-pattern anchoring: patterns like the one targeting credential flags are unanchored and can produce malformed log lines when they match substrings inside longer environment-variable names.

Confidence Score: 3/5

  • Safe to merge for most users, but the missing timeout on both hooks is an unresolved operational risk in slow or broken environments.
  • The PR resolves the critical bugs raised in earlier review rounds (escaping, directory creation, jq-failure passthrough, Authorization pattern). The remaining issues — missing timeout, duplicated filter chain, and unanchored redaction substring matching — are maintainability or edge-case concerns rather than correctness regressions. No new critical bugs were introduced.
  • hooks/hooks.json — both new hook entries (lines 148–165) lack a "timeout" field and share identical but separately maintained redaction filter chains.

Important Files Changed

Filename Overview
hooks/hooks.json Adds two PostToolUse Bash hooks for audit logging and cost tracking. The PR addresses several previously raised issues (mkdir -p,

Sequence Diagram

sequenceDiagram
    participant CC as Claude Code
    participant H1 as bash-audit-hook
    participant H2 as cost-tracker-hook
    participant H3 as downstream hooks
    participant L1 as ~/.claude/bash-commands.log
    participant L2 as ~/.claude/cost-tracker.log

    CC->>H1: stdin (JSON payload)
    H1->>H1: mkdir -p ~/.claude
    H1->>H1: INPUT=$(cat)
    H1->>H1: jq format + redact
    H1->>L1: append log line (|| true)
    H1->>H2: printf INPUT (stdin passthrough)

    H2->>H2: mkdir -p ~/.claude
    H2->>H2: INPUT=$(cat)
    H2->>H2: jq format + redact
    H2->>L2: append log line (|| true)
    H2->>H3: printf INPUT (stdin passthrough)

    H3->>CC: result
Loading

Reviews (5): Last reviewed commit: "fix: use [: ]* instead of s* for Authori..." | Re-trigger Greptile

Comment thread hooks/hooks.json Outdated
Comment thread hooks/hooks.json Outdated
Comment thread hooks/hooks.json Outdated

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
hooks/hooks.json (1)

52-63: Consider redacting sensitive fragments before logging raw commands

These logs can capture secrets passed in CLI args (tokens, passwords, keys). Add lightweight redaction in jq before append (e.g., common --token=..., password=..., AKIA... patterns).

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

In `@hooks/hooks.json` around lines 52 - 63, Update the Bash command hooks (the
"command" strings under the matcher "tool == \"Bash\"" and the earlier audit
hook writing to ~/.claude/bash-commands.log and ~/.claude/cost-tracker.log) to
perform lightweight redaction in the jq pipeline before appending: apply
additional gsub() calls to mask common secret patterns (e.g., replace
--token=..., password=..., secret=..., AKIA[0-9A-Z]{16}, and similar CLI
key/secret patterns with a fixed placeholder like "<REDACTED>") and ensure you
run those gsub transformations on the (.tool_input.command // "?") value (the
same place you already use gsub("\\n"; " ")) so logged lines collapse newlines
and have secrets redacted before writing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@hooks/hooks.json`:
- Around line 52-63: Update the Bash command hooks (the "command" strings under
the matcher "tool == \"Bash\"" and the earlier audit hook writing to
~/.claude/bash-commands.log and ~/.claude/cost-tracker.log) to perform
lightweight redaction in the jq pipeline before appending: apply additional
gsub() calls to mask common secret patterns (e.g., replace --token=...,
password=..., secret=..., AKIA[0-9A-Z]{16}, and similar CLI key/secret patterns
with a fixed placeholder like "<REDACTED>") and ensure you run those gsub
transformations on the (.tool_input.command // "?") value (the same place you
already use gsub("\\n"; " ")) so logged lines collapse newlines and have secrets
redacted before writing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a68fef8f-7e17-453c-8d77-7bd9b84c00b7

📥 Commits

Reviewing files that changed from the base of the PR and between 6b2de1b and 26acc01.

📒 Files selected for processing (2)
  • hooks/hooks.json
  • rules/hooks.md

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="hooks/hooks.json">

<violation number="1" location="hooks/hooks.json:52">
P1: New PostToolUse Bash audit hook consumes stdin without forwarding payload, which can break downstream hooks that read stdin JSON.</violation>

<violation number="2" location="hooks/hooks.json:62">
P1: New PostToolUse cost-tracker hook also drains stdin and does not emit payload for subsequent hooks.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread hooks/hooks.json Outdated
Comment thread hooks/hooks.json Outdated
@affaan-m

Copy link
Copy Markdown
Owner

Nice improvement to audit log readability. Will review. 🦞

@affaan-m

Copy link
Copy Markdown
Owner

This PR has developed merge conflicts with main. Please rebase your branch to resolve. Once all conflicts are resolved and CI passes, we can proceed with review. Thanks for your contribution!

@affaan-m affaan-m left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Three issues to fix before merge:

  1. Merge conflicts — rebase on latest main
  2. Security: no secret redaction — bash commands may contain API keys/tokens in arguments. Add redaction for common patterns (--token, -H Authorization, env var assignments)
  3. Stdin consumptioncat | jq consumes stdin without forwarding to downstream PostToolUse hooks. Use tee or read+echo pattern to preserve the payload

Happy to merge once addressed.

Add gsub("\\n"; " ") to jq filters in bash audit log and cost-tracker
hooks so multi-line commands produce single-line log entries, preventing
breakage in downstream line-based parsing.

Fixes affaan-m#734
@cskwork cskwork force-pushed the fix/issue-734-multiline-command-logging branch from 26acc01 to 6483834 Compare March 23, 2026 00:26
Comment thread hooks/hooks.json Outdated
Addresses review feedback: PostToolUse hooks now preserve stdin
for subsequent hooks by echoing $INPUT back to stdout after
processing. Changed ; to && for proper error propagation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="hooks/hooks.json">

<violation number="1" location="hooks/hooks.json:151">
P2: Hook pass-through output is incorrectly conditional on successful logging, so log failures can suppress required stdout JSON.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread hooks/hooks.json Outdated
Comment thread hooks/hooks.json Outdated
Comment thread hooks/hooks.json Outdated
Comment thread hooks/hooks.json Outdated
- Use semicolons instead of && so printf passthrough always runs
  even if jq fails
- Add || true after jq to prevent non-zero exit on parse errors
- Use printf '%s\n' instead of echo for safe binary passthrough
- Fix Authorization pattern to handle 'Bearer <token>' with space
- Add ASIA (STS temp credentials) alongside AKIA redaction
- Add GitHub token patterns (ghp_, gho_, ghs_, github_pat_)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread hooks/hooks.json Outdated
jq's ONIG regex engine interprets s* as literal 's' zero-or-more,
not \s* (whitespace). This caused 'Authorization: Bearer <token>'
to only redact 'Authorization:' and leak the actual token.

Using [: ]* avoids the JSON/jq double-escape issue entirely and
correctly matches both 'Authorization: Bearer xyz' and
'Authorization:xyz' patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread hooks/hooks.json
"hooks": [
{
"type": "command",
"command": "#!/bin/bash\nmkdir -p ~/.claude; INPUT=$(cat);\necho \"$INPUT\" | jq -r '\"[\" + (now | todate) + \"] \" + ((.tool_input.command // \"?\") | gsub(\"\n\"; \" \") | gsub(\"--token[= ][^ ]*\"; \"--token=<REDACTED>\") | gsub(\"Authorization:[: ]*[^ ]*[: ]*[^ ]*\"; \"Authorization:<REDACTED>\") | gsub(\"AKIA[A-Z0-9]{16}\"; \"<REDACTED>\") | gsub(\"ASIA[A-Z0-9]{16}\"; \"<REDACTED>\") | gsub(\"password[= ][^ ]*\"; \"password=<REDACTED>\") | gsub(\"ghp_[A-Za-z0-9_]+\"; \"<REDACTED>\") | gsub(\"gho_[A-Za-z0-9_]+\"; \"<REDACTED>\") | gsub(\"ghs_[A-Za-z0-9_]+\"; \"<REDACTED>\") | gsub(\"github_pat_[A-Za-z0-9_]+\"; \"<REDACTED>\"))' >> ~/.claude/bash-commands.log 2>/dev/null || true;\nprintf '%s\n' \"$INPUT\""

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.

P2 Redaction pattern substring-matches environment variable names

The gsub pattern that targets credential flags has no word-boundary anchor. A command like export MY_MASTER_PASS=value would have the credential correctly stripped, but the remaining prefix (MY_MASTER_) would be left dangling in the log entry, producing a confusing/malformed line.

Narrowing the pattern to match only a leading boundary (e.g. (^|[^A-Za-z0-9_])) or restricting to explicit CLI flag syntax (--flag[= ][^ ]*) would reduce false-positive substring matches without weakening redaction coverage. The same concern applies to the cost-tracker hook on line 161.

@affaan-m affaan-m merged commit eacf3a9 into affaan-m:main Mar 31, 2026
4 checks passed
peiking88 pushed a commit to peiking88/everything-claude-code that referenced this pull request Apr 4, 2026
…#741)

* fix(hooks): collapse multi-line commands in bash audit logs

Add gsub("\\n"; " ") to jq filters in bash audit log and cost-tracker
hooks so multi-line commands produce single-line log entries, preventing
breakage in downstream line-based parsing.

Fixes affaan-m#734

* fix: forward stdin to downstream hooks using echo pattern

Addresses review feedback: PostToolUse hooks now preserve stdin
for subsequent hooks by echoing $INPUT back to stdout after
processing. Changed ; to && for proper error propagation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: make stdin passthrough unconditional and broaden secret redaction

- Use semicolons instead of && so printf passthrough always runs
  even if jq fails
- Add || true after jq to prevent non-zero exit on parse errors
- Use printf '%s\n' instead of echo for safe binary passthrough
- Fix Authorization pattern to handle 'Bearer <token>' with space
- Add ASIA (STS temp credentials) alongside AKIA redaction
- Add GitHub token patterns (ghp_, gho_, ghs_, github_pat_)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use [: ]* instead of s* for Authorization whitespace matching

jq's ONIG regex engine interprets s* as literal 's' zero-or-more,
not \s* (whitespace). This caused 'Authorization: Bearer <token>'
to only redact 'Authorization:' and leak the actual token.

Using [: ]* avoids the JSON/jq double-escape issue entirely and
correctly matches both 'Authorization: Bearer xyz' and
'Authorization:xyz' patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FrancescoRosciano pushed a commit to FRosciano-Mambo/everything-claude-code that referenced this pull request Jun 1, 2026
…#741)

* fix(hooks): collapse multi-line commands in bash audit logs

Add gsub("\\n"; " ") to jq filters in bash audit log and cost-tracker
hooks so multi-line commands produce single-line log entries, preventing
breakage in downstream line-based parsing.

Fixes affaan-m#734

* fix: forward stdin to downstream hooks using echo pattern

Addresses review feedback: PostToolUse hooks now preserve stdin
for subsequent hooks by echoing $INPUT back to stdout after
processing. Changed ; to && for proper error propagation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: make stdin passthrough unconditional and broaden secret redaction

- Use semicolons instead of && so printf passthrough always runs
  even if jq fails
- Add || true after jq to prevent non-zero exit on parse errors
- Use printf '%s\n' instead of echo for safe binary passthrough
- Fix Authorization pattern to handle 'Bearer <token>' with space
- Add ASIA (STS temp credentials) alongside AKIA redaction
- Add GitHub token patterns (ghp_, gho_, ghs_, github_pat_)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use [: ]* instead of s* for Authorization whitespace matching

jq's ONIG regex engine interprets s* as literal 's' zero-or-more,
not \s* (whitespace). This caused 'Authorization: Bearer <token>'
to only redact 'Authorization:' and leak the actual token.

Using [: ]* avoids the JSON/jq double-escape issue entirely and
correctly matches both 'Authorization: Bearer xyz' and
'Authorization:xyz' patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

bug(hooks): cost-tracker and bash audit logs break on multi-line commands

2 participants