-
Notifications
You must be signed in to change notification settings - Fork 0
feat: track post-merge CI/CD runs in ci-watcher #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,16 @@ | ||
| #!/bin/bash | ||
| # CI Watcher — tracks GitHub Actions runs and reports results via Pulse | ||
| # | ||
| # Automatically triggered by Claude Code PostToolUse hook on `gh pr create` | ||
| # or `git push`. Watches all CI runs for the current commit and sends | ||
| # pass/fail notifications to the active Pulse channel. | ||
| # Automatically triggered by Claude Code PostToolUse hook on `gh pr create`, | ||
| # `git push`, or `gh pr merge`. Watches all CI/CD runs for the relevant | ||
| # commit and sends pass/fail notifications to the active Pulse channel. | ||
| # | ||
| # Features: | ||
| # - Finds Pulse port via ancestor Claude Code PID (session-aware) | ||
| # - Deduplicates notifications (lock file per commit + session) | ||
| # - Supports .ci-watch-ignore for skipping specific workflows | ||
| # - Tracks multiple concurrent runs, reports each as it completes | ||
| # - Tracks post-merge CI/CD runs (e.g. deploy pipelines on main) | ||
| # - 10 minute timeout with warning notification | ||
| # | ||
| # Requirements: gh, jq, curl | ||
|
|
@@ -46,17 +47,20 @@ set -euo pipefail | |
| # Parse hook input | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| CMD=$(jq -r '.tool_input.command // empty' 2>/dev/null) | ||
| STDIN_JSON=$(cat) | ||
| CMD=$(echo "$STDIN_JSON" | jq -r '.tool_input.command // empty' 2>/dev/null) | ||
| if [ -z "$CMD" ]; then exit 0; fi | ||
|
|
||
| # Only trigger on gh pr create or git push (not --delete) | ||
| if echo "$CMD" | grep -qE "gh pr create"; then | ||
| : | ||
| # Determine trigger type | ||
| TRIGGER="" | ||
| if echo "$CMD" | grep -qE "gh pr merge"; then | ||
| TRIGGER="merge" | ||
| elif echo "$CMD" | grep -qE "gh pr create"; then | ||
| TRIGGER="pr" | ||
| elif echo "$CMD" | grep -qE "git push" && ! echo "$CMD" | grep -qE "--delete|-d"; then | ||
| : | ||
| else | ||
| exit 0 | ||
| TRIGGER="push" | ||
| fi | ||
| if [ -z "$TRIGGER" ]; then exit 0; fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Resolve Pulse port via ancestor Claude Code PID | ||
|
|
@@ -82,21 +86,6 @@ CLAUDE_PID=$(find_claude_pid) || { exit 0; } | |
| PULSE_PORT=$(cat ~/.pulse/"$CLAUDE_PID".port 2>/dev/null || echo "") | ||
| if [ -z "$PULSE_PORT" ]; then exit 0; fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Dedup: skip if another ci-watcher is already tracking this commit | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| SHA=$(git rev-parse HEAD 2>/dev/null || echo "") | ||
| if [ -z "$SHA" ]; then exit 0; fi | ||
|
|
||
| LOCK_DIR=~/.pulse/locks | ||
| mkdir -p "$LOCK_DIR" | ||
| LOCK_FILE="$LOCK_DIR/${CLAUDE_PID}-${SHA}.lock" | ||
| if ! mkdir "$LOCK_FILE" 2>/dev/null; then | ||
| exit 0 | ||
| fi | ||
| trap 'rmdir "$LOCK_FILE" 2>/dev/null' EXIT | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Helpers | ||
| # --------------------------------------------------------------------------- | ||
|
|
@@ -109,12 +98,50 @@ notify() { | |
| -d "{\"text\":\"$text\",\"source\":\"ci\",\"level\":\"$level\"}" 2>/dev/null || true | ||
| } | ||
|
|
||
| # Get repo | ||
| REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo "") | ||
| if [ -z "$REPO" ]; then exit 0; fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Load .ci-watch-ignore | ||
| # Resolve commit SHA based on trigger type | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo "") | ||
| if [ -z "$REPO" ]; then exit 0; fi | ||
| if [ "$TRIGGER" = "merge" ]; then | ||
| # Extract PR number from command (gh pr merge 123 or gh pr merge URL) | ||
| PR_NUM=$(echo "$CMD" | grep -oE 'gh pr merge [^ ]+' | awk '{print $NF}' | grep -oE '[0-9]+' | head -1) | ||
| if [ -z "$PR_NUM" ]; then exit 0; fi | ||
|
|
||
| # Get the merge commit SHA from the merged PR | ||
| PR_JSON=$(gh pr view "$PR_NUM" --repo "$REPO" --json mergeCommit,baseRefName 2>/dev/null || echo "{}") | ||
| SHA=$(echo "$PR_JSON" | jq -r '.mergeCommit.oid // empty' 2>/dev/null) | ||
|
|
||
| if [ -z "$SHA" ]; then | ||
| # Merge might not be complete yet, wait and retry | ||
| sleep 5 | ||
| PR_JSON=$(gh pr view "$PR_NUM" --repo "$REPO" --json mergeCommit,baseRefName 2>/dev/null || echo "{}") | ||
| SHA=$(echo "$PR_JSON" | jq -r '.mergeCommit.oid // empty' 2>/dev/null) | ||
|
Comment on lines
+118
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This logic retries Useful? React with 👍 / 👎. |
||
| fi | ||
| if [ -z "$SHA" ]; then exit 0; fi | ||
| else | ||
| SHA=$(git rev-parse HEAD 2>/dev/null || echo "") | ||
| if [ -z "$SHA" ]; then exit 0; fi | ||
| fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Dedup: skip if another ci-watcher is already tracking this commit | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| LOCK_DIR=~/.pulse/locks | ||
| mkdir -p "$LOCK_DIR" | ||
| LOCK_FILE="$LOCK_DIR/${CLAUDE_PID}-${SHA}.lock" | ||
| if ! mkdir "$LOCK_FILE" 2>/dev/null; then | ||
| exit 0 | ||
| fi | ||
| trap 'rmdir "$LOCK_FILE" 2>/dev/null' EXIT | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Load .ci-watch-ignore | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| IGNORE_FILE="$(git rev-parse --show-toplevel 2>/dev/null)/.ci-watch-ignore" | ||
| IGNORE_PATTERNS=() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gh pr mergetarget without assuming numeric argThe new merge path only derives
PR_NUMby extracting digits from the first token aftergh pr merge, so it exits for valid invocations likegh pr merge(no argument), branch-name arguments, or flag-first forms. The GitHub CLI synopsis allows[<number> | <url> | <branch>]and also supports no argument (current branch PR), so this parser causes the watcher to silently skip post-merge CI tracking for common merge flows.Useful? React with 👍 / 👎.