Skip to content

feat(WEB-4974): Per-tool-call project attribution across all coding-agent hooks#214

Open
gowshik450526511 wants to merge 6 commits into
stagingfrom
WEB-4974
Open

feat(WEB-4974): Per-tool-call project attribution across all coding-agent hooks#214
gowshik450526511 wants to merge 6 commits into
stagingfrom
WEB-4974

Conversation

@gowshik450526511

@gowshik450526511 gowshik450526511 commented Jul 12, 2026

Copy link
Copy Markdown
Contributor
Screenshot 2026-07-23 at 5 43 04 PM Screenshot 2026-07-24 at 12 06 22 AM (2)

What

Every hook (claude-code, cursor, copilot, codex, augment) now resolves the git project ("org/repo") per tool call and sends turn-level cwd + project on the Stop exchange.

  • Resolution: candidate dir → walk up to the nearest .git (worktree-aware, filesystem-only) → git remote get-url origin (10s-bounded, cached once per repo per turn) → lowercased org/repo. Fully fail-open — None on any failure, never raises/blocks.
  • File tools resolve from the file path (Copilot/Augment join relative paths onto the tracked shell dir); shell tools from absolute tokens in the command, with cd tracking (abs/relative/~, now including multi-line commands) and the tracked dir as fallback; Codex prefers its explicit workdir.
  • Copilot staging-merge resolution preserves both staging's per-Stop dedup (watermark + text_sig) and shell-dir tracking — skipped already-forwarded shell calls still follow cd.
  • Augment gains the resolver for the first time (was previously unattributed).
  • Baked UNBOUND_GATEWAY_URL defaults verified as https://api.getunbound.ai in all five hooks.

Review notes

Elite-pr-review pass: approved; fixed the multi-line cd regex gap it found. Known follow-ups (accuracy, non-blocking): Augment models launch-process as a persistent shell (prefer per-event cwd later); abs-path candidates outside any repo suppress the shell-dir fallback by design ("report facts"); file:// remotes parse to junk org/repo.

Tests

113 passed (copilot hooks + binary suites); all five hooks byte-compile.

🤖 Generated with Claude Code


Note

Medium Risk
Adds bounded git subprocess calls and path heuristics on the Stop path only; failures are fail-open but wrong repo attribution is possible in edge cases (multi-repo turns, non-GitHub remotes).

Overview
Adds per-tool-call project (org/repo from git origin) and turn-level cwd + project on Stop/audit payloads across augment, claude-code, codex, copilot, and cursor hooks.

Resolution is shared in spirit: walk paths to a .git root, cache git remote get-url origin per repo per turn, and fail open with None. File tools use paths (relative paths join the tracked shell dir where needed); shell tools use absolute path tokens, optional Codex workdir, and cd tracking (including multi-line commands). Claude Code splits read vs write tool sets; Copilot still advances shell dir on dedup-skipped shell calls; Augment gets attribution for the first time.

Each tool entry in the exchange can carry its own project; the exchange-level project is a fallback for prompt-only or tool-less rows.

Reviewed by Cursor Bugbot for commit 5a8efff. Bugbot is set up for automated code reviews on this repo. Configure here.

Greptile Summary

This PR adds per-tool-call project attribution across the coding-agent hooks. The main changes are:

  • Git project resolution from tool paths, shell commands, and working directories.
  • Per-turn caching for git remote lookups.
  • Shell directory tracking for commands that change directories.
  • Turn-level cwd and project fields on Stop exchanges.
  • Augment support for project attribution.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
augment/hooks/unbound.py Adds project resolution, shell directory tracking, and Stop exchange attribution for Augment.
claude-code/hooks/unbound.py Adds per-tool project attribution and carries Stop working directory context into exchange construction.
codex/hooks/unbound.py Adds project attribution for parsed Codex tool calls and Stop exchange metadata.
copilot/hooks/unbound.py Adds per-call project attribution while preserving transcript deduplication and shell directory tracking.
cursor/unbound.py Adds project attribution from Cursor workspace roots, file paths, shell commands, and tool inputs.

Reviews (4): Last reviewed commit: "Merge remote-tracking branch 'origin/sta..." | Re-trigger Greptile

Context used:

  • Rule used - Ensure that the confidence score is always within ... (source)

Learned From
websentry-ai/ai-gateway-data#448

Add cwd and project (GitHub org/repo) to PreToolUse, UserPromptSubmit, and
StopEvent hook requests. Includes helper functions to resolve GitHub origin
from git remotes. Enables better analytics, policy scoping, and debugging by
tracking which repository and working context triggered each event.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@gowshik450526511
gowshik450526511 requested a review from a team July 12, 2026 10:39
@gowshik450526511 gowshik450526511 changed the title Augment hook events with project context and cwd for analytics [WEB-4974]Add project info and working directory to hook requests Jul 12, 2026
@gowshik450526511
gowshik450526511 changed the base branch from main to staging July 12, 2026 10:41
Comment thread claude-code/hooks/unbound.py
Comment thread claude-code/hooks/unbound.py
Comment on lines +1484 to +1492
if '://' in url:
# scheme://[user@]host/ORG/repo(.git)
after_scheme = url.split('://', 1)[1]
path = after_scheme.split('/', 1)[1] if '/' in after_scheme else ''
elif ':' in url:
# scp-like: [user@]host:ORG/repo(.git)
path = url.split(':', 1)[1]
else:
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Non-GitHub Remotes Become GitHub Projects

When origin points at GitLab, Bitbucket, or another host with an org/repo path, this parser still returns that path and _get_project sends it as the project value. A repo like git@gitlab.com:acme/private.git is reported as acme/private, so downstream analytics or project-scoped policy can treat it as the unrelated GitHub repo with the same owner and name.

Comment on lines +1530 to +1532
result = subprocess.run(
['git', '-C', cwd, 'remote', 'get-url', 'origin'],
capture_output=True, text=True, timeout=10,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Interactive Hooks Wait On Git

Every PreToolUse and UserPromptSubmit call now runs git -C <cwd> remote get-url origin before sending the hook request. If the worktree is on a slow network mount or the .git directory blocks, each interactive hook can wait up to 10 seconds before failing open, making normal tool use appear hung.

Comment thread claude-code/hooks/unbound.py Outdated

@vigneshsubbiah16 vigneshsubbiah16 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🛡️ Automated Security Review (consensus)

4 findings — 2 high-confidence, 2 to triage. Reviewers: Cursor, Claude, Semgrep, Gitleaks.

🔴 HIGH — Unverified project identifier unsafe for policy scoping

  • File: claude-code/hooks/unbound.py:1476-1541
  • Impact: project is derived client-side from git remote get-url origin without verifying github.com; non-GitHub remotes (GitLab/Bitbucket) and colon-containing local paths (e.g. C:/...) are parsed into GitHub-shaped org/repo values, enabling policy evasion via git remote set-url or cross-host name collisions.
  • Fix: Treat project as advisory analytics metadata on the server, not a policy principal; in _github_remote_path/_get_project, return None unless the host is exactly github.com, and reject local/file:///drive-prefix remotes before SCP-style : splitting.
  • Flagged by: Claude, Greptile, Cursor

🔴 HIGH — Raw local filesystem paths sent on every hook request

  • File: claude-code/hooks/unbound.py:1390, 1566, 1657
  • Impact: Full cwd values (e.g. /home/alice/clients/acme-secret) are attached to every PreToolUse, UserPromptSubmit, and Stop payload, continuously exposing usernames, client names, and internal project codenames to the external hook API.
  • Fix: If repository context suffices, send only normalized project; otherwise redact cwd (basename, strip home prefix) and document collection in data-handling notes.
  • Flagged by: Claude, Greptile

🟡 TRIAGE — Synchronous git subprocess on interactive hook path

  • File: claude-code/hooks/unbound.py:1526-1529
  • Impact: Every PreToolUse and UserPromptSubmit blocks on git -C <cwd> remote get-url origin (10s timeout); slow or hung worktrees can stall tool use and silently drop project context on timeout.
  • Fix: Lower timeout to ~1–2s, cache cwd → project per session, and run git lookup off the critical path where possible.
  • Flagged by: Claude, Greptile

🟡 TRIAGE — Stop exchange uses first-seen cwd, not latest

  • File: claude-code/hooks/unbound.py:1595-1658
  • Impact: build_llm_exchange captures the first non-empty cwd and never updates it; if the working directory changes mid-session, Stop analytics and project attribution can target the wrong repository.
  • Fix: Prefer the cwd from the Stop event (or the most recent event) when resolving project for the exchange payload.
  • Flagged by: Cursor

Semgrep reported pre-existing insecure-file-permissions hits at lines 1892/2192 (unchanged by this diff). Gitleaks: no secrets detected.


🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head 8e49fb58 · 2026-07-12T10:42Z

@gowshik450526511 gowshik450526511 changed the title [WEB-4974]Add project info and working directory to hook requests Add project context and cwd to hook events Jul 13, 2026
Use cwd captured at session start (_get_session_cwd) instead of
per-request event.get('cwd') for more stable project tracking across
the entire conversation.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Comment thread claude-code/hooks/unbound.py Outdated
'model': model,
'event_name': 'tool_use',
'cwd': session_cwd,
'project': _get_project(session_cwd),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Project Lookup Still Blocks This still derives project inside process_pre_tool_use, and _get_project runs git -C <cwd> remote get-url origin with a 10 second timeout. The new cache stores only the cwd string, not the derived project, so a slow or blocked worktree can still make every tool invocation wait on git before the hook request is sent. Cache the project value itself or move this lookup out of the blocking hook path.

Comment thread claude-code/hooks/unbound.py Outdated
else:
return None
path = path.strip('/')
return path or None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Non-GitHub remotes mislabeled

Medium Severity

_github_remote_path and _get_project derive org/repo from any origin URL without checking that the host is GitHub. Remotes such as GitLab or Bitbucket can be sent as GitHub-shaped project values, skewing analytics and GitHub-scoped policy.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a64168c. Configure here.

Comment thread claude-code/hooks/unbound.py Outdated

@vigneshsubbiah16 vigneshsubbiah16 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🛡️ Automated Security Review (consensus)

5 findings — 4 high-confidence, 1 to triage. Reviewers: Cursor, Claude, Semgrep, Gitleaks.

🔴 HIGH — Client-spoofable project identifier for policy scoping

claude-code/hooks/unbound.py:1544
Impact: project is derived entirely client-side from git remote get-url origin with no host validation; any worktree can report an arbitrary org/repo (including non-GitHub remotes parsed as GitHub-shaped paths) and inherit or evade project-scoped gateway policy.
Fix: Never use client-reported project as a policy key; validate github.com (or enterprise host) in _github_remote_path and enforce scoping server-side with an authenticated allowlist.
Flagged by: Claude, Greptile, Cursor, Lead

🔴 HIGH — Session cwd/project silently absent on most hook invocations

claude-code/hooks/unbound.py:2242
Impact: _SESSION_CWD_CACHE is in-memory in a process spawned per hook event, so SessionStart writes are invisible to later PreToolUse/UserPromptSubmit/Stop runs; cwd/project are usually None, causing project-scoped controls to silently never match with no failure signal.
Fix: Persist session cwd and resolved project to disk (keyed by session_id, alongside existing session state); treat missing project as an explicit restrictive bucket; log resolution errors instead of except Exception: return None.
Flagged by: Claude, Cursor, Lead

🔴 HIGH — Raw local filesystem paths exfiltrated on every hook event

claude-code/hooks/unbound.py:1392
Impact: Full cwd values (e.g. /home/alice/clients/acme-secret) are sent to the external hook API on every tool use, prompt, and stop event, exposing usernames, customer names, and internal project codenames.
Fix: If project satisfies analytics/scoping needs, omit cwd from gateway payloads or send a redacted/hashed form only.
Flagged by: Greptile, Claude, Lead

🔴 HIGH — Synchronous git subprocess on interactive hook path (10s cap)

claude-code/hooks/unbound.py:1535
Impact: _get_project shells out to git -C <cwd> remote get-url origin on every PreToolUse/UserPromptSubmit/Stop call; slow or blocked worktrees can delay tool gating for up to 10s per invocation (local stall/DoS on the PreToolUse gate).
Fix: Resolve and cache project once at SessionStart (persisted across processes), or move lookup off the blocking hook path entirely.
Flagged by: Greptile, Cursor

🟡 TRIAGE — Stop events may attribute wrong repository after mid-session cwd change

claude-code/hooks/unbound.py:1661
Impact: build_llm_exchange uses session-start cwd only; if the working directory changes mid-conversation, stop-event project can mislabel the turn for analytics and policy attribution.
Fix: Prefer the Stop event's cwd or the latest audit-entry cwd when building the exchange payload.
Flagged by: Cursor


🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head a64168c4 · 2026-07-13T06:34Z

Gowshik T and others added 3 commits July 15, 2026 13:21
…gent hooks

- claude-code/codex/copilot/cursor: resolve the git project (org/repo) per
  tool call (file path / shell-dir tracking) and ride it on each tool_use
  entry; send turn-level cwd + project on the Stop exchange
- augment: add the same per-call resolver (_project_for_tool_use, .git walk,
  cached origin lookups) and cwd/project on the Stop exchange

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	copilot/hooks/unbound.py
The cd-target regex boundary lacked \n, so "git add .\ncd /repoB\nmake"
never updated the tracked shell dir and later calls were attributed to the
old repo. Applied to claude-code, codex, copilot, and augment hooks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread claude-code/hooks/unbound.py
Comment thread cursor/unbound.py
@gowshik450526511 gowshik450526511 changed the title Add project context and cwd to hook events feat(WEB-4974): Per-tool-call project attribution across all coding-agent hooks Jul 15, 2026

@vigneshsubbiah16 vigneshsubbiah16 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🛡️ Automated Security Review (consensus)

5 findings — 1 high-confidence, 4 to triage. Reviewers: Cursor, Claude, Semgrep, Gitleaks.

Client-derived project is spoofable — unsafe for policy scoping

Severity: Medium · Confidence: 🔴 HIGH
Location: claude-code/hooks/unbound.py:1537 (mirrored in augment/hooks/unbound.py, codex/hooks/unbound.py, copilot/hooks/unbound.py, cursor/unbound.py)
Impact: project is derived client-side from paths in model-controlled tool args/commands and whatever origin exists there; an injected agent or user can git remote set-url or plant a .git tree and have calls attributed to a trusted org/repo, bypassing project-scoped policy if the server trusts this field.
Fix: Treat project/cwd as untrusted telemetry server-side; on the client, require github.com (or your canonical host) before emitting org/repo.
Flagged by: Claude, Cursor, Greptile

git -C runs against model-influenced directories

Severity: Low · Confidence: 🟡 TRIAGE
Location: claude-code/hooks/unbound.py:1632 (_project_for_tool_use_get_project; same pattern in all five hooks)
Impact: Absolute paths extracted from shell/MCP arguments can trigger git -C <path> remote get-url inside attacker-planted repos; remote get-url is read-only today, but this establishes a pattern of invoking git in untrusted trees each turn.
Fix: Restrict candidates to session/workspace roots, or read .git/config directly instead of shelling out to git.
Flagged by: Claude

Unbounded path candidates can stall Stop processing

Severity: Low · Confidence: 🟡 TRIAGE
Location: claude-code/hooks/unbound.py:1560 (_ABS_PATH_RE_find_git_root / _get_project; mirrored in codex/copilot/cursor)
Impact: Commands or JSON args with many distinct absolute-path tokens can drive unbounded ancestor walks and up to one 10s git subprocess per distinct repo root on the Stop path, delaying audit forwarding (fail-open does not cap latency).
Fix: Cap candidates per tool call (e.g. 3–5), cap root_projects lookups per turn, and lower the git timeout for this best-effort path.
Flagged by: Claude

Cursor shell events do not track cd across the turn

Severity: Medium · Confidence: 🟡 TRIAGE
Location: cursor/unbound.py:1278
Impact: afterShellExecution uses per-event cwd, command absolute paths, or workspace_roots, but never updates a running shell directory after a cd-only command; later shell events without cwd can mis-attribute project to the workspace root.
Fix: Port _CD_TARGET_RE / _next_shell_dir shell-dir tracking used in the other hooks.
Flagged by: Cursor

Claude Code file tools ignore workspace-relative paths

Severity: Medium · Confidence: 🟡 TRIAGE
Location: claude-code/hooks/unbound.py:1614
Impact: Read/Write/Edit/Grep/Glob attribution only considers absolute paths, while Bash already tracks shell cd; relative file paths after cd leave per-call project unset and can fall back to the wrong turn-level repo.
Fix: Join relative file paths onto shell_dir (as Augment/Copilot already do) before _find_git_root.
Flagged by: Cursor

Previously acknowledged (not re-flagged)

  • file:// / colon-split remote parsing (e.g. Windows C:/… → junk org/repo) — known follow-up in PR description (file:// remotes parse to junk org/repo).
  • Raw local cwd sent to the hook API — accepted design; per-tool cwd + project attribution is the PR’s stated purpose.
  • Absolute-path candidates outside any repo suppress shell-dir fallback — intentional “report facts” behavior per PR review notes.
  • Augment models launch-process as a persistent shell — known accuracy follow-up (non-blocking) per PR description.

🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head b94abcce · 2026-07-15T08:36Z

# Conflicts:
#	claude-code/hooks/unbound.py
#	cursor/unbound.py

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

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

There are 4 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5a8efff. Configure here.

_ABS_PATH_RE = re.compile(r'(?:^|[\s"\'=(])(/[^\s"\';|&<>()]+)')
# `cd <target>` occurrences — absolute, ~-rooted, or relative — used to track
# the persistent shell's working directory across the turn's Bash calls.
_CD_TARGET_RE = re.compile(r'(?:^|[;&|\n]\s*|\bthen\s+|\bdo\s+)cd\s+(["\']?)([^\s"\';|&]+)\1')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Chained cd after && missed

Medium Severity

The new _CD_TARGET_RE only treats cd as starting a segment after ;, a single |, &, newline, or shell keywords—not after && or ||. Commands like npm test && cd packages/app never update the tracked shell directory, so later Bash calls and fallbacks can be attributed to the wrong repo.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5a8efff. Configure here.

Comment thread codex/hooks/unbound.py
candidates.extend(_ABS_PATH_RE.findall(command))
shell_dir = _next_shell_dir(command, shell_dir)
if not candidates and shell_dir:
candidates.append(shell_dir)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Relative Codex workdir mis-resolved

Medium Severity

For exec_command, a non-empty relative workdir is pushed onto candidates as-is, which prevents appending the tracked shell_dir. _find_git_root then resolves that path against the hook process cwd, not the session cwd, so attribution can point at the wrong repository or miss entirely.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5a8efff. Configure here.

Comment thread cursor/unbound.py
'tool_use_id': _resolve_tool_use_id(event),
'project': _project_for_paths(
[os.path.dirname(file_path)] if isinstance(file_path, str) and file_path.startswith('/') else [],
root_projects)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor skips relative file paths

Medium Severity

Per-call project resolution for beforeReadFile and afterFileEdit only runs when file_path is absolute. Workspace-relative paths produce no candidates, so those tool rows get no project even though workspace_cwd is already captured for the turn.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5a8efff. Configure here.

if not candidates and shell_dir:
candidates.append(shell_dir)
for candidate in candidates:
root = _find_git_root(candidate)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Agentic Security Review
Severity: MEDIUM
_project_for_tool_use accepts model/tool-controlled path candidates (from file_path, absolute command tokens, and tracked shell directory) and immediately resolves git roots before running git -C <root> remote get-url origin, without enforcing that candidates stay inside allowed workspace roots.

This can attribute and emit project values from repositories outside the intended workspace boundary when tool inputs reference external paths.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit 5a8efff. Configure here.

@vigneshsubbiah16 vigneshsubbiah16 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🛡️ Automated Security Review (consensus)

1 finding — 0 high-confidence, 1 to triage. Reviewers: Cursor, Claude, Semgrep, Gitleaks.


[MEDIUM] Per-call project spoofable via shell command path tokens

claude-code/hooks/unbound.py:1773 (same pattern: codex/hooks/unbound.py:1265, copilot/hooks/unbound.py:1510, augment/hooks/unbound.py:1478, cursor/unbound.py:1348)

🟡 TRIAGE — For Bash/shell tools, _ABS_PATH_RE.findall(command) is consulted before the tracked shell_dir fallback; the command string is agent-controlled. A prompt-injected or misbehaving agent can prepend a benign absolute path (e.g. : /home/u/benign-repo/x; …) so per-call project and downstream analytics/policy scoping record activity under the wrong repo.

Fix: Treat command-scraped paths as lowest-confidence — prefer Codex workdir, hook-event cwd, and tracked shell_dir over the first absolute token in the command; optionally attach a resolution-source field so the backend can down-rank command-token attributions in policy decisions.

Flagged by: Claude


Previously acknowledged (not re-raised)

  • Raw local cwd sent to the hook API — PR design: turn-level cwd + project are intentional attribution fields.
  • file://, SCP-colon, and non-GitHub origin URLs → junk or GitHub-shaped org/repo — Known accuracy follow-up in PR description (non-blocking).
  • Augment models launch-process as a persistent shell — Known accuracy follow-up; per-event cwd preferred later.
  • Absolute-path candidates outside any repo suppress shell_dir fallback — By design (“report facts”).
  • &&/|| chained cd, relative file/workdir paths, missing shell-dir tracking, Codex relative workdir resolution — Attribution accuracy gaps on the Stop-only path; already flagged by prior bot threads, not security regressions.
  • Semgrep insecure-file-permissions (0o755/$BITS) and sqlalchemy-execute-raw-query (cursor/unbound.py:495) — Pre-existing patterns outside this PR’s diff (executable hook install + local DB helper); not introduced or worsened here.
  • Gitleaks — No secrets detected.

🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head 5a8efff2 · 2026-07-23T10:12Z

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