feat(ops): agent identity scheme (trailer hook + PR wrapper + docs)#59
Conversation
Implements FLO-210 (parts 2 & 3 of the FLO-209 scheme): idempotent prepare-commit-msg trailer hook, a gh pr create wrapper that adds the "Agent: <Name>" line and agent:<slug> label, and the operations doc describing env aliases, trailers, and PR attribution. Co-Authored-By: Paperclip <noreply@paperclip.ing> Co-authored-by: Daedalus <daedalus@agents.flopbut.local>
|
Warning Review limit reached
More reviews will be available in 54 minutes and 2 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR establishes an agent identity system for attributing commits and pull requests to AI agents within a shared codebase. It provides documentation, a commit hook, a PR wrapper script, and an installer to track agent authorship through git environment variables, commit trailers, and GitHub labels without requiring separate accounts. ChangesAgent Identity Attribution System
Sequence DiagramsequenceDiagram
participant Agent
participant AdapterConfig as Paperclip API
participant GitEnv as Git Env Vars
participant CommitHook as prepare-commit-msg Hook
participant PRScript as agent-pr.sh
participant GitHub
Agent->>AdapterConfig: PATCH /api/agents/{id} with env
AdapterConfig->>GitEnv: Set GIT_AUTHOR_NAME/EMAIL
GitEnv->>CommitHook: Read env on commit
CommitHook->>GitHub: Append Co-authored-by trailer
Agent->>PRScript: Create PR with agent name
PRScript->>GitHub: Create agent:{slug} label
PRScript->>GitHub: Create PR with Agent: name line
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Distinguish FlopBut agents under the shared GitHub token via three layers: author/committer env, an idempotent commit-trailer hook, and PR attribution (Agent: line + agent:<slug> label). Agents self-apply their own adapter env with scripts/agent-set-git-identity.sh — the only path that both passes the API permission check and safely preserves redacted per-agent secret env. Co-Authored-By: Paperclip <noreply@paperclip.ing> Co-authored-by: Daedalus <daedalus@agents.flopbut.local>
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
scripts/install-agent-git-hooks.sh (1)
24-24: ⚡ Quick winConsider warning if chmod fails.
Line 24 silently ignores chmod failures with
|| true. If making the hooks executable fails, git will ignore the hooks silently. While the "best-effort" approach is reasonable, users might not realize the hooks aren't working.
⚠️ Suggested warning-chmod +x "$HOOKS_DIR"/* 2>/dev/null || true +if ! chmod +x "$HOOKS_DIR"/* 2>/dev/null; then + echo "warning: could not make hooks executable; they may not run" >&2 +fi🤖 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 `@scripts/install-agent-git-hooks.sh` at line 24, The chmod call currently swallows failures; replace the silent "chmod +x \"$HOOKS_DIR\"/* 2>/dev/null || true" with logic that runs the chmod, captures its exit status, and prints a warning to stderr if it fails (e.g., using echo/printf to >&2) mentioning HOOKS_DIR and that hook files may not be executable; ensure you still treat it as best-effort (do not exit non-zero) but surface a clear warning when chmod returns a non-zero status so users know hooks might not run.scripts/git-hooks/prepare-commit-msg (2)
19-23: ⚡ Quick winComment mentions amending but code doesn't skip amend source.
Line 19 states the hook skips "when amending an existing message" but the case statement only skips merge/squash commits, not "commit" source (used for
--amend). The idempotency check at line 34 prevents duplication during amends, so the behavior is safe. However, the comment should either be clarified to match the actual behavior or the code should explicitly handle the "commit" source if amends should be skipped.📝 Suggested clarification
-# Skip for merge/squash commits and when amending an existing message — those -# already carry trailers or are machine-generated. +# Skip for merge/squash commits — those are machine-generated and the +# agent is already credited via author/committer fields. +# For amends, the idempotency check below prevents duplication. case "$COMMIT_SOURCE" in merge|squash) exit 0 ;; esac🤖 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 `@scripts/git-hooks/prepare-commit-msg` around lines 19 - 23, The comment in prepare-commit-msg claims it skips amend ("amending an existing message") but the case on COMMIT_SOURCE only handles merge|squash; either update the comment to accurately state that amends (COMMIT_SOURCE=commit) are not skipped because an idempotency check later prevents duplication, or modify the case to include "commit" so amends are explicitly skipped. Locate the COMMIT_SOURCE case block in prepare-commit-msg and either change the comment text to reflect actual behavior (mention idempotency check) or add "commit" to the case pattern (merge|squash|commit) to make the code match the comment.
33-36: 💤 Low valueComment slightly misleading about case-sensitivity scope.
Line 33 states "case-insensitive on the trailer key, exact on name+email" but
grep -qiFapplies case-insensitivity to the entire line, including name and email. In practice this is safe sinceGIT_AUTHOR_NAMEandGIT_AUTHOR_EMAILare set by Paperclip and remain consistent, but the comment could be more accurate.📝 Suggested clarification
-# Already present (case-insensitive on the trailer key, exact on name+email)? +# Already present? (checks case-insensitively across the entire trailer line) if grep -qiF "$TRAILER" "$COMMIT_MSG_FILE"; then🤖 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 `@scripts/git-hooks/prepare-commit-msg` around lines 33 - 36, The comment above the grep invocation is misleading: the command using grep -qiF applies case-insensitivity to the entire matched line (including GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL), not just the trailer key. Update the comment near the TRIALER/COMMIT_MSG_FILE check to state that grep -qiF performs case-insensitive matching for the whole trailer string (key, name and email) and, if you intended to only ignore case for the trailer key, adjust the comment to call that out or switch to a matching approach that normalizes only the key portion.
🤖 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 `@docs/operations/agent-identity.md`:
- Around line 55-63: Update the two example commit-trailer code blocks to use a
consistent "Co-authored-by: Name <email>" capitalization (capital C, lowercase
"oauthored-by") to match the prepare-commit-msg hook output, and add a language
specifier by changing the fences to ```text for both blocks so they render with
proper syntax highlighting and accessibility; specifically replace the
"Co-Authored-By" / "Co-authored-by" lines in the
docs/operations/agent-identity.md examples with "Co-authored-by: Paperclip
<noreply@paperclip.ing>" and "Co-authored-by: Wayland
<wayland@agents.flopbut.local>" inside ```text fences.
In `@scripts/agent-pr.sh`:
- Around line 29-38: Update the argument parser in the while/case loop to accept
both space-separated and equals-separated forms (e.g., --title value and
--title=value) by splitting on '=' when present and assigning to TITLE and BODY
accordingly (referencing variables TITLE, BODY and array PASS and the while/case
block), ensure any unrecognized args still go into PASS, and add a validation
step before invoking gh pr create that checks TITLE is non-empty and exits with
a clear error if missing (referencing the final gh pr create call so the script
never runs with an empty --title).
In `@scripts/git-hooks/prepare-commit-msg`:
- Around line 38-42: The code that appends TRAILER to COMMIT_MSG_FILE doesn't
guarantee a blank line before the trailer; change the logic so if the file is
non-empty and its last line is not empty (e.g., test via tail -n1
"$COMMIT_MSG_FILE" | grep -qv '^$' or check last char(s)), append an extra
newline before writing TRAILER; specifically update the block handling
COMMIT_MSG_FILE/TAILER so it ensures a blank separator line (resulting in
"\n\nTrailer\n" when necessary) rather than only a single trailing newline.
---
Nitpick comments:
In `@scripts/git-hooks/prepare-commit-msg`:
- Around line 19-23: The comment in prepare-commit-msg claims it skips amend
("amending an existing message") but the case on COMMIT_SOURCE only handles
merge|squash; either update the comment to accurately state that amends
(COMMIT_SOURCE=commit) are not skipped because an idempotency check later
prevents duplication, or modify the case to include "commit" so amends are
explicitly skipped. Locate the COMMIT_SOURCE case block in prepare-commit-msg
and either change the comment text to reflect actual behavior (mention
idempotency check) or add "commit" to the case pattern (merge|squash|commit) to
make the code match the comment.
- Around line 33-36: The comment above the grep invocation is misleading: the
command using grep -qiF applies case-insensitivity to the entire matched line
(including GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL), not just the trailer key.
Update the comment near the TRIALER/COMMIT_MSG_FILE check to state that grep
-qiF performs case-insensitive matching for the whole trailer string (key, name
and email) and, if you intended to only ignore case for the trailer key, adjust
the comment to call that out or switch to a matching approach that normalizes
only the key portion.
In `@scripts/install-agent-git-hooks.sh`:
- Line 24: The chmod call currently swallows failures; replace the silent "chmod
+x \"$HOOKS_DIR\"/* 2>/dev/null || true" with logic that runs the chmod,
captures its exit status, and prints a warning to stderr if it fails (e.g.,
using echo/printf to >&2) mentioning HOOKS_DIR and that hook files may not be
executable; ensure you still treat it as best-effort (do not exit non-zero) but
surface a clear warning when chmod returns a non-zero status so users know hooks
might not run.
🪄 Autofix (Beta)
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
Run ID: a56b39ae-57d9-4317-8f85-08206cb6d5b1
📒 Files selected for processing (4)
docs/operations/agent-identity.mdscripts/agent-pr.shscripts/git-hooks/prepare-commit-msgscripts/install-agent-git-hooks.sh
| ``` | ||
| Co-Authored-By: Paperclip <noreply@paperclip.ing> | ||
| ``` | ||
|
|
||
| each commit gets a second co-author line for the authoring agent: | ||
|
|
||
| ``` | ||
| Co-authored-by: Wayland <wayland@agents.flopbut.local> | ||
| ``` |
There was a problem hiding this comment.
Inconsistent trailer capitalization and missing language specifiers.
Lines 56 and 62 show different capitalizations: "Co-Authored-By" vs "Co-authored-by". While Git treats trailer keys case-insensitively, the documentation should be consistent. The hook at line 31 in scripts/git-hooks/prepare-commit-msg outputs "Co-authored-by" (capital C, lowercase a/b), so this example should match.
Additionally, both code blocks are missing language specifiers, which reduces syntax highlighting and accessibility.
📝 Proposed fix
-```
+```text
Co-Authored-By: Paperclip <noreply@paperclip.ing>each commit gets a second co-author line for the authoring agent:
- -Co-authored-by: Wayland <wayland@agents.flopbut.local> +text
+Co-Authored-By: Wayland wayland@agents.flopbut.local
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 55-55: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 61-61: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 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 `@docs/operations/agent-identity.md` around lines 55 - 63, Update the two
example commit-trailer code blocks to use a consistent "Co-authored-by: Name
<email>" capitalization (capital C, lowercase "oauthored-by") to match the
prepare-commit-msg hook output, and add a language specifier by changing the
fences to ```text for both blocks so they render with proper syntax highlighting
and accessibility; specifically replace the "Co-Authored-By" / "Co-authored-by"
lines in the docs/operations/agent-identity.md examples with "Co-authored-by:
Paperclip <noreply@paperclip.ing>" and "Co-authored-by: Wayland
<wayland@agents.flopbut.local>" inside ```text fences.
| TITLE="" | ||
| BODY="" | ||
| PASS=() | ||
| while [ $# -gt 0 ]; do | ||
| case "$1" in | ||
| --title) TITLE="$2"; shift 2 ;; | ||
| --body) BODY="$2"; shift 2 ;; | ||
| *) PASS+=("$1"); shift ;; | ||
| esac | ||
| done |
There was a problem hiding this comment.
Argument parsing doesn't handle --flag=value format or validate required flags.
The parser only handles space-separated flags (--title value), not equals-separated (--title=value). If a user passes --title="feat: xyz", it won't be extracted into $TITLE and will pass through to $PASS, causing --title to appear twice in the final gh pr create command (line 49). While the last value typically wins, this is confusing.
Additionally, --title is required by gh pr create, but the script doesn't validate that it was provided. If missing, line 49 would call gh pr create --title "", which may fail or prompt interactively.
🛡️ Proposed fix with validation and better parsing
TITLE=""
BODY=""
PASS=()
while [ $# -gt 0 ]; do
case "$1" in
- --title) TITLE="$2"; shift 2 ;;
- --body) BODY="$2"; shift 2 ;;
+ --title)
+ if [ $# -lt 2 ]; then
+ echo "error: --title requires a value" >&2
+ exit 1
+ fi
+ TITLE="$2"; shift 2 ;;
+ --title=*) TITLE="${1#--title=}"; shift ;;
+ --body)
+ if [ $# -lt 2 ]; then
+ echo "error: --body requires a value" >&2
+ exit 1
+ fi
+ BODY="$2"; shift 2 ;;
+ --body=*) BODY="${1#--body=}"; shift ;;
*) PASS+=("$1"); shift ;;
esac
done
+
+if [ -z "$TITLE" ]; then
+ echo "error: --title is required" >&2
+ exit 1
+fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| TITLE="" | |
| BODY="" | |
| PASS=() | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| --title) TITLE="$2"; shift 2 ;; | |
| --body) BODY="$2"; shift 2 ;; | |
| *) PASS+=("$1"); shift ;; | |
| esac | |
| done | |
| TITLE="" | |
| BODY="" | |
| PASS=() | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| --title) | |
| if [ $# -lt 2 ]; then | |
| echo "error: --title requires a value" >&2 | |
| exit 1 | |
| fi | |
| TITLE="$2"; shift 2 ;; | |
| --title=*) TITLE="${1#--title=}"; shift ;; | |
| --body) | |
| if [ $# -lt 2 ]; then | |
| echo "error: --body requires a value" >&2 | |
| exit 1 | |
| fi | |
| BODY="$2"; shift 2 ;; | |
| --body=*) BODY="${1#--body=}"; shift ;; | |
| *) PASS+=("$1"); shift ;; | |
| esac | |
| done | |
| if [ -z "$TITLE" ]; then | |
| echo "error: --title is required" >&2 | |
| exit 1 | |
| fi |
🤖 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 `@scripts/agent-pr.sh` around lines 29 - 38, Update the argument parser in the
while/case loop to accept both space-separated and equals-separated forms (e.g.,
--title value and --title=value) by splitting on '=' when present and assigning
to TITLE and BODY accordingly (referencing variables TITLE, BODY and array PASS
and the while/case block), ensure any unrecognized args still go into PASS, and
add a validation step before invoking gh pr create that checks TITLE is
non-empty and exits with a clear error if missing (referencing the final gh pr
create call so the script never runs with an empty --title).
| # Ensure a blank line separates the body from the trailer block, then append. | ||
| if [ -s "$COMMIT_MSG_FILE" ] && [ -n "$(tail -c1 "$COMMIT_MSG_FILE")" ]; then | ||
| printf '\n' >> "$COMMIT_MSG_FILE" | ||
| fi | ||
| printf '%s\n' "$TRAILER" >> "$COMMIT_MSG_FILE" |
There was a problem hiding this comment.
Blank line logic doesn't ensure trailer separation.
The comment at line 38 states the intent to "ensure a blank line separates the body from the trailer block," but the implementation only ensures the file ends with a single newline, not two. Git trailers should be preceded by a blank line.
Current behavior:
- If file =
"message\n": appends"Trailer\n"→ result:"message\nTrailer\n"(no blank line) - If file =
"message": adds"\n", appends"Trailer\n"→ result:"message\nTrailer\n"(still no blank line)
Expected:
- Result should be
"message\n\nTrailer\n"(blank line before trailer)
🐛 Proposed fix
-# Ensure a blank line separates the body from the trailer block, then append.
-if [ -s "$COMMIT_MSG_FILE" ] && [ -n "$(tail -c1 "$COMMIT_MSG_FILE")" ]; then
- printf '\n' >> "$COMMIT_MSG_FILE"
-fi
-printf '%s\n' "$TRAILER" >> "$COMMIT_MSG_FILE"
+# Ensure a blank line separates the body from the trailer block, then append.
+if [ -s "$COMMIT_MSG_FILE" ]; then
+ # Add one newline if file doesn't end with one, then always add blank line
+ if [ -n "$(tail -c1 "$COMMIT_MSG_FILE")" ]; then
+ printf '\n' >> "$COMMIT_MSG_FILE"
+ fi
+ printf '\n' >> "$COMMIT_MSG_FILE"
+fi
+printf '%s\n' "$TRAILER" >> "$COMMIT_MSG_FILE"🤖 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 `@scripts/git-hooks/prepare-commit-msg` around lines 38 - 42, The code that
appends TRAILER to COMMIT_MSG_FILE doesn't guarantee a blank line before the
trailer; change the logic so if the file is non-empty and its last line is not
empty (e.g., test via tail -n1 "$COMMIT_MSG_FILE" | grep -qv '^$' or check last
char(s)), append an extra newline before writing TRAILER; specifically update
the block handling COMMIT_MSG_FILE/TAILER so it ensures a blank separator line
(resulting in "\n\nTrailer\n" when necessary) rather than only a single trailing
newline.
Implements the FlopBut agent-identity scheme from FLO-209 (parts 2 & 3) and lands the tooling for part 1.
What's here
scripts/git-hooks/prepare-commit-msg— idempotent co-author trailer hook driven by$GIT_AUTHOR_NAME/$GIT_AUTHOR_EMAIL.scripts/install-agent-git-hooks.sh— pointscore.hooksPathat the hook dir (per repo/worktree).scripts/agent-pr.sh—gh pr createwrapper: adds theAgent: <Name>line and theagent:<slug>label idempotently.docs/operations/agent-identity.md— full scheme: identity table, env aliases, trailers, PR attribution.Verification
This very PR was opened with
scripts/agent-pr.sh(note theAgent:line below and theagent:daedaluslabel). The control commit shows author/committer + both co-author trailers — see FLO-210.Refs: FLO-210
Agent: Daedalus
Summary by CodeRabbit
Documentation
New Features