Skip to content

PROF-16456: Enforce the pre-PR agentic review with a skill, marker script and hooks - #935

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 7 commits into
mainfrom
pre-pr-review-gate
Nov 4, 2026
Merged

gh-worker-dd-mergequeue-cf854d[bot] merged 7 commits into
mainfrom
pre-pr-review-gate

Conversation

@rkennke

@rkennke rkennke commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?:

Turns the agentic pre-PR review from prose in AGENTS.md into a procedure that tools carry out and enforce.

Piece Role
.agents/skills/pre-pr-review/SKILL.md (.claude/skills symlinks to .agents/skills) The procedure: install the hook, run the reviewer, check comments against the comment budget and for plan/spec references, write the marker.
utils/review-marker.py Writes and checks a per-branch marker certifying the reviewed commits; install-hook installs the push gate.
.githooks/pre-push Refuses pushes to a branch with an open PR unless the pushed commits match the marker.
.claude/hooks/require-fresh-review.py Claude Code PreToolUse hook for what pre-push cannot see: gh pr create/new/ready.
.pi/extensions/require-fresh-review.ts Thin Pi adapter: runs the same hook from Pi's tool_call event and blocks on its exit 2. Pi already finds the skill in .agents/skills/.
AGENTS.md Keeps the obligation and points to the above. Drops "mechanical gate" from the comment budget.

Motivation:

  • The comment budget was labeled a "mechanical gate", but nothing enforced it, and it needs judgment. Judgment belongs in the review, so the skill makes it an explicit review item.
  • The review procedure lived in AGENTS.md, which is advisory. Agents had to follow a digest recipe by hand, and nothing checked the result.
  • The old recipe went stale whenever reviewed changes were committed.
  • Fixes pushed to an open PR were not covered. On PR Bound reference-chain pass rate and root-enum frequency (PROF-16384) #925, a follow-up fix added eight comments citing a spec that had been removed from the PR.

Additional Notes:

Design decisions, flagged for reviewer acknowledgment:

  • The marker certifies commits, not the working tree. write refuses uncommitted tracked changes and stores the git patch-id --verbatim of merge-base..HEAD. Context lines anchor hunk positions, so an upstream change right next to the branch's hunks errs toward stale. That forces a re-review rather than letting unreviewed code through. Requires git 2.39+.
  • The hook is installed as a shim in <git-common-dir>/hooks/, never through core.hooksPath. A repo-local core.hooksPath would replace a global one. On Datadog machines that disables the dd-git-hooks secret scanner. The global wrapper chains to .git/hooks, which I verified for both the main checkout and worktrees.
  • The Claude Code hook matches raw command text on purpose. Three review rounds showed that parsing shell syntax kept leaking. Raw matching covers subshells, bash -c, multi-line commands and continuations. The accepted cost is a false block when the text is only quoted while the marker is stale. Where the target is ambiguous, it blocks: --head, conflicting bases, cd/pushd/env -C/GIT_DIR, and git steps that move HEAD in the same command.
  • The hooks never run code from the checkout. The shim, pre-push and the Claude Code hook load their code from refs/remotes/origin/main and run it with python3 -I. Checking out an untrusted branch, such as a fork PR, and then pushing therefore runs only reviewed code. The ref is fully qualified, so a tag named origin/main cannot substitute it. -I keeps planted stdlib-named files at the worktree root from being imported. The skill runs install-hook and write the same way, so markers are written by the version that checks them.
  • Accepted consequence: the gate does nothing until this PR is on main. Both hooks allow everything when refs/remotes/origin/main lacks the scripts. That also covers clones without an origin remote. Claude Code's own loading of .claude/ hooks from the session's checkout falls under its workspace-trust model and is out of scope.
  • Gaps that remain: the push gate is opt-in (install-hook), the PR-command hook covers only Claude Code and Pi (Pi loads project extensions only after project trust), and CI cannot see the local marker. The ticket's production check covers the first real use.

Agentic review: seven /code-review (high) passes, then two sphinx range reviews: one over the full change set (1671c13..79b1c19) and one over the Pi/test commit (79b1c19..bc7d270). Neither found Critical/High; the test gaps the first one flagged are covered. The bot findings raised on ready-for-review (gh-merge-base, qualified digest base, tag-safe branch names, repo selectors) are fixed in 09055de and were reviewed with /code-review, whose one High (the skill's unqualified merge-base) is fixed in the same commit. Every Critical/High finding is fixed. These Medium/Low findings are open for reviewer judgment:

  • Medium: gh pr create checks the local tip, while the PR opens from the remote branch, which can differ.
  • Medium: write does not normalize --base, so --base main never matches the hook's origin/main.
  • Medium: the fail-open applies to any failing git show (for example, a clone whose main remote is upstream), and it is silent.
  • Low: tests that move origin/main restore it on the next line instead of in a trap.
  • Medium (skill): step 3's comment checks run before step 4's fixes, so fixes are not re-checked against the comment rules.
  • Medium (skill): step 3 still mentions reviewer fix flows, which step 2 now rules out.
  • Medium (skill): the --report-json path is not pinned to a gitignored location such as .sphinx/.
  • Low (skill): no step says to read the JSON report; --session has no defined source in report-only mode.
  • Low: test_review_marker.sh runs only by hand, like the other utils/tests scripts.
  • Low: the Pi adapter tests need Node 22.6+ (--experimental-strip-types); on older Node they fail instead of skipping.
  • Medium: -R is read from the whole matched text, which can start at an earlier gh command on the same line (gh pr view -R x 34 && gh pr ready 12).
  • Medium: gh pr create -R other/repo without --base is checked against the checkout repo's default branch.
  • Medium: only origin/ bases are qualified; a base on another remote (upstream/main) can still be shadowed by a same-named tag.
  • Low: a repeated -R takes the first value (gh takes the last); -B elsewhere on the line overrides gh-merge-base; a gh-merge-base given as a full ref is not normalized; no tests for -Rx or the detached-HEAD fallback.
  • Low: has_open_pr ignores which remote is pushed to.
  • Low: gh pr ready doesn't parse the --repo= form.
  • Low: SKILL.md overstates when a rebase keeps the marker fresh.
  • Low: the hook starts Python on every Bash call.

How to test the change?:

  • bash utils/tests/test_review_marker.sh (136 hermetic cases, including the Pi adapter driven through a stub Pi API under node; each in a throwaway repo with a bare origin and a gh stub; global git config is ignored).
  • Mutation checks: disabling each fix (digest anchoring, refspec handling, raw matching, directory and state-change guards, shim upgrade, settings guard, origin/main sourcing, -I, the qualified ref) fails at least one test. The trust-model tests plant payloads in the checkout (.githooks/pre-push, utils/review-marker.py, argparse.py, subprocess.py) and a tag named origin/main, then assert that no payload ran.
  • utils/check-tracked-artifacts.py, utils/check-doc-index.py.
  • Manual: in a Claude Code session in the repo, pre-pr-review should appear in the skill list (through the .claude/skills symlink).

For Datadog employees:

  • If this PR touches code that signs or publishes builds or packages, or handles
    credentials of any kind, I've requested a security review (run the dd:platform-security-review
    skill, or file a request via the PSEC review form).
    bewaire also runs automatically on every PR.
  • This PR doesn't touch any of that.
  • JIRA: PROF-16456

🤖 Generated with Claude Code

rkennke and others added 3 commits November 4, 2026 12:46
utils/review-marker.py records that a review covered a branch and checks
it later. The marker certifies commits: write refuses uncommitted tracked
changes and stores the verbatim patch-id of the diff from the merge base
to HEAD. An advancing base or a clean rebase keeps it fresh unless the
base changed lines next to the branch's hunks; context lines anchor each
hunk's position, so that case errs toward stale. Any change to the
branch's diff, whitespace or position included, makes it stale. Markers
live per branch, named by hash, in the git common dir, so every worktree
sees them and they are never committed. git 2.39+ is required and checked.

install-hook writes a pre-push shim into the common hooks dir instead of
setting core.hooksPath: a repo-local hooksPath would replace global
hooks, such as Datadog's secret scanner, which chain to .git/hooks.

PROF-16456

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skill lives in .agents/skills/ for harnesses that read that path;
.claude/skills is a symlink to it. It installs the pre-push shim, runs
the available reviewer, adds the comment-budget and plan/spec-reference
checks, and writes the marker.

.githooks/pre-push refuses to update a branch with an open PR unless the
pushed commits match its marker. It reads the real refs from git, so it
covers every harness, human pushes and HEAD or sha refspecs. It runs the
local check first and asks gh about open PRs only when that fails, and
fails open when gh cannot answer.

The Claude Code PreToolUse hook covers what pre-push cannot see:
gh pr create/new/ready. It matches the raw command text, with backslash
continuations joined, instead of parsing shell syntax, so subshells,
bash -c and multi-line commands cannot hide the call; the accepted cost
is a false block when the text is only quoted while the marker is stale.
Where the text cannot pin the target down it blocks: --head, conflicting
bases, anything that may switch checkouts (cd, pushd, env -C/--chdir,
GIT_DIR), and git steps that move HEAD in the same command, since the
hook runs before the command. gh pr ready is checked against the PR's
head commit on GitHub. The settings entry exits 0 when the hook file is
missing, so an older checkout cannot block every call.

PROF-16456

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ate"

The comment budget needs judgment, so no tool enforces it; the agentic
review checks it instead. The review section keeps the obligation, now
also covering PRs marked ready and fixes pushed to an open PR, and points
to the pre-pr-review skill, review-marker.py and the hooks. The
inline-vs-doc rule now sends procedures to skills and enforcement to
scripts, hooks and CI.

PROF-16456

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dd-octo-sts

dd-octo-sts Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Scan-Build Report

User:runner@runnervm48cm3
Working Directory:/home/runner/work/java-profiler/java-profiler/ddprof-lib/src/test/make
Command Line:make -j4 all
Clang Version:Ubuntu clang version 18.1.3 (1ubuntu1)
Date:Wed Nov 4 13:23:08 2026

Bug Summary

Bug TypeQuantityDisplay?
All Bugs2
Logic error
Dereference of null pointer1
Unused code
Dead initialization1

Reports

Bug Group Bug Type ▾ File Function/Method Line Path Length
Unused codeDead initializationreferenceChains.cppapplyOomRamp4971
Logic errorDereference of null pointerperfEvents_linux.cppunregisterThread72026

Comment thread .agents/skills/pre-pr-review/SKILL.md Outdated
when the work is stacked. Commit the change set first — the marker certifies
commits, so uncommitted work is not covered.

2. **Review.** Use the strongest reviewer the harness provides: `/sphinx review`,

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.

Since the step 4 will do the triage and fixes, we should use /sphinx review --report-json (or /sphinx review --report-json <path> to store the findings in a file) and skip the built-in auto-fix loop.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, done in 79b1c19. Step 2 now runs report-only, with no reviewer fix flow. Steps 3 and 4 do the triage and fixes, so there is one fix loop. The review of this change also showed that the range has to be named explicitly: without one, sphinx reviews only unpushed changes, and the marker would certify code nobody reviewed. So the step now reads /sphinx review range $(git merge-base <base> HEAD)..$(git rev-parse HEAD) --report-json <path>, with /code-review given the same range as fallback.

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.

Hm, I will check it. It should look at 'branch changes' as well, but there might be some issue there - I had some before, thanks to the worktrees etc.

Comment thread .agents/skills/pre-pr-review/SKILL.md Resolved
Comment thread .claude/settings.json Resolved
rkennke and others added 3 commits November 4, 2026 13:19
The pre-push shim ran the checkout's .githooks/pre-push, which loaded the
checkout's review-marker.py, and the Claude Code hook preferred the
checkout's script too. Checking out an untrusted branch, such as a fork
PR, and then pushing or running gh pr create would execute that branch's
code with the user's credentials; git itself never runs versioned hooks
for this reason.

The shim, pre-push and the Claude Code hook now take their code from
refs/remotes/origin/main via git show and run it with python3 -I -c. The
ref is fully qualified so a tag named origin/main cannot substitute it,
and -I keeps the worktree root off sys.path so stdlib-named files planted
there are not imported. The skill runs install-hook and write the same
way, so markers are written by the version that checks them. When the ref
lacks the scripts, the hooks allow everything. Claude Code's own loading
of .claude/ hooks from the session's checkout is out of scope.

PROF-16456

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Name the reviewed range explicitly, merge-base to HEAD: without a range,
sphinx reviews only unpushed changes and /code-review only recent ones,
so the marker would certify code nobody reviewed. Run /sphinx review
range with --report-json and no reviewer fix flow; steps 3 and 4 already
triage and fix.

PROF-16456

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.pi/extensions/require-fresh-review.ts runs the same hook from Pi's
tool_call event: it passes the bash command as the hook's JSON payload
and blocks on exit 2. Pi already discovers the skill in .agents/skills.
Its tests drive the extension through a stub Pi API under node.

New test cases pin the git 2.39 version boundary, tag pushes never
consulting gh, an explicit --base winning over the default branch, the
-H short form, and -R/--repo being forwarded to gh pr view.

PROF-16456

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dd-octo-sts

dd-octo-sts Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

CI Test Results

Run: #41869045727 | Commit: 6aa5253 | Duration: 23m 13s (longest job)

✅ All 32 test jobs passed

Status Overview

JDK glibc-aarch64/debug glibc-amd64/debug musl-aarch64/debug musl-amd64/debug
8 - ✅ - -
8-ibm - ✅ - -
8-j9 ✅ ✅ - -
8-librca - - ✅ ✅
8-orcl - ✅ - -
11 - ✅ - -
11-j9 ✅ ✅ - -
11-librca - - ✅ ✅
17 ✅ ✅ - -
17-graal ✅ ✅ - -
17-j9 ✅ ✅ - -
17-librca - - ✅ ✅
21 ✅ ✅ - -
21-graal ✅ ✅ - -
21-librca - - ✅ ✅
25 ❔ ✅ - -
25-graal ✅ ✅ - -
25-librca - - ✅ ✅

Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled

Summary: Total: 32 | Passed: 31 | Failed: 0


Updated: 2026-11-04 17:30:14 UTC

@rkennke
rkennke marked this pull request as ready for review November 4, 2026 12:55
@rkennke
rkennke requested a review from a team as a code owner November 4, 2026 12:55
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-04T13:01:27.940637Z bc7d270 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bc7d270eeb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread .claude/hooks/require-fresh-review.py Resolved

@datadog-datadog-prod-us1 datadog-datadog-prod-us1 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.

Bits Code Review: FAIL

Two of the most critical issues are: the patch-id digest does not encode hunk location, so moving a reviewed edit between identically-contexted blocks leaves the marker falsely fresh; and the hook ignores the branch-level gh-merge-base config, validating against the wrong base for stacked or release-targeted branches. Additional distinct issues affect repository-selector handling in gh pr ready, stale base resolution after PR retargeting, remote-head-to-local-branch mapping, unqualified merge-base ref resolution, and ambiguous branch-name shortening when a tag shadows a branch.

Open Bits AI session

🤖 Bits Code Review · Commit bc7d270

Comment thread utils/review-marker.py Resolved
Comment thread .githooks/pre-push Resolved
Comment thread .claude/hooks/require-fresh-review.py Resolved
Comment thread .claude/hooks/require-fresh-review.py Resolved
Comment thread .claude/hooks/require-fresh-review.py Resolved
Comment thread utils/review-marker.py Resolved
Comment thread utils/review-marker.py Resolved
- gh pr create without --base targets branch.<name>.gh-merge-base before
  the repo default; the hook now checks that base, without a network call.
- The repo selector of gh pr ready is read from the whole command, so
  `gh -R o/r pr ready N` and `--repo=o/r` keep their repository.
- The digest's merge base uses refs/remotes/origin/<branch>, and so does
  the skill's reviewed range, so a tag named origin/<branch> cannot shift
  either of them.
- Branch names come from the full symbolic ref: next to a same-named tag,
  --short yields "heads/<branch>" and the marker landed under that name.

utils/README.md lists the accepted limits: edits moved between identical
contexts keep the digest, pre-push does not see a retargeted PR base, and
gh pr ready does not map a renamed push back to its local branch.

PROF-16456

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dd-octo-sts

dd-octo-sts Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

✅ All 40 integration tests passed

📊 Dashboard · 👷 Pipeline · 📦 09055deb

@dd-octo-sts

dd-octo-sts Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Reliability & Chaos Results

✅ All reliability & chaos checks passed Pipeline: https://gitlab.ddbuild.io/DataDog/java-profiler/-/pipelines/150158477

@rkennke
rkennke requested a review from jbachorik November 4, 2026 14:00
@rkennke

rkennke commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-11-04 16:01:52 UTC ℹ️ Start processing command /merge


2026-11-04 16:01:55 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in main is approximately 31m (p90).


2026-11-04 16:36:23 UTC ℹ️ MergeQueue: This merge request was merged

@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit 6aa5253 into main Nov 4, 2026
260 of 270 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the pre-pr-review-gate branch November 4, 2026 16:36
@github-actions github-actions Bot added this to the 1.53.0 milestone Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants