Skip to content

Commit 38a3b80

Browse files
authored
Session work\n\nAuto-commit at session end to preserve work in progress.\n\nSession-ID: 579d5b4c-f770-40e8-8489-0d22372479ff\nSession-Timestamp: 2026-01-23T03:48:55.746Z\nBranch: 52-fix/review-prs-49-51-and-then-see-if-we-can-\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> (#53)
1 parent bbff424 commit 38a3b80

3 files changed

Lines changed: 165 additions & 1 deletion

File tree

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/bin/bash
2+
# Link current branch to GitHub issue using native "Development" section
3+
#
4+
# Usage: link-branch-to-issue.sh <issue-number>
5+
#
6+
# This creates a proper GitHub link that shows in the issue's "Development"
7+
# sidebar, avoiding namespace collisions between issues and PRs.
8+
#
9+
# Note: createLinkedBranch only works for NEW branches. For existing branches,
10+
# we fall back to storing the mapping in .claude/logs/branch-issues.json
11+
12+
set -euo pipefail
13+
14+
ISSUE_NUMBER="${1:-}"
15+
if [ -z "$ISSUE_NUMBER" ]; then
16+
echo "No issue number provided, skipping link"
17+
exit 0
18+
fi
19+
20+
# Get repo info from git remote
21+
REMOTE_URL=$(git remote get-url origin 2>/dev/null || echo "")
22+
if [ -z "$REMOTE_URL" ]; then
23+
echo "No git remote found"
24+
exit 0
25+
fi
26+
27+
# Extract owner/repo from remote URL
28+
# Handles: git@github.com:owner/repo.git, https://github.com/owner/repo.git
29+
REPO_FULL=$(echo "$REMOTE_URL" | sed -E 's#^(git@github\.com:|https://github\.com/)##' | sed 's/\.git$//')
30+
REPO_OWNER=$(echo "$REPO_FULL" | cut -d'/' -f1)
31+
REPO_NAME=$(echo "$REPO_FULL" | cut -d'/' -f2)
32+
33+
if [ -z "$REPO_OWNER" ] || [ -z "$REPO_NAME" ]; then
34+
echo "Could not parse repo from remote: $REMOTE_URL"
35+
exit 0
36+
fi
37+
38+
BRANCH=$(git branch --show-current)
39+
if [ -z "$BRANCH" ]; then
40+
echo "Not on a branch"
41+
exit 0
42+
fi
43+
44+
echo "Linking branch '$BRANCH' to issue #$ISSUE_NUMBER in $REPO_OWNER/$REPO_NAME"
45+
46+
# Get issue node ID (required for GraphQL mutation)
47+
ISSUE_ID=$(gh api graphql -f query="
48+
query {
49+
repository(owner: \"$REPO_OWNER\", name: \"$REPO_NAME\") {
50+
issue(number: $ISSUE_NUMBER) { id }
51+
}
52+
}" --jq '.data.repository.issue.id' 2>/dev/null || echo "")
53+
54+
if [ -z "$ISSUE_ID" ] || [ "$ISSUE_ID" = "null" ]; then
55+
echo "Could not find issue #$ISSUE_NUMBER"
56+
exit 0
57+
fi
58+
59+
# Check if branch already exists on remote
60+
BRANCH_EXISTS=$(git ls-remote --heads origin "$BRANCH" 2>/dev/null | wc -l)
61+
62+
if [ "$BRANCH_EXISTS" -gt 0 ]; then
63+
# Branch exists - createLinkedBranch won't work
64+
# Check if it's already linked
65+
EXISTING_LINK=$(gh api graphql -f query="
66+
query {
67+
repository(owner: \"$REPO_OWNER\", name: \"$REPO_NAME\") {
68+
issue(number: $ISSUE_NUMBER) {
69+
linkedBranches(first: 10) {
70+
nodes { ref { name } }
71+
}
72+
}
73+
}
74+
}" --jq ".data.repository.issue.linkedBranches.nodes[].ref.name | select(. == \"$BRANCH\")" 2>/dev/null || echo "")
75+
76+
if [ -n "$EXISTING_LINK" ]; then
77+
echo "Branch '$BRANCH' is already linked to issue #$ISSUE_NUMBER"
78+
else
79+
echo "Branch already exists on remote. GitHub's createLinkedBranch only works for new branches."
80+
echo "The link will be established when a PR is created with 'Closes #$ISSUE_NUMBER' in the body."
81+
fi
82+
else
83+
# Branch doesn't exist on remote - try createLinkedBranch
84+
OID=$(git rev-parse HEAD)
85+
86+
RESULT=$(gh api graphql -f query="
87+
mutation {
88+
createLinkedBranch(input: {
89+
issueId: \"$ISSUE_ID\",
90+
oid: \"$OID\",
91+
name: \"$BRANCH\"
92+
}) {
93+
linkedBranch {
94+
ref { name }
95+
}
96+
}
97+
}" 2>&1 || echo "FAILED")
98+
99+
if echo "$RESULT" | grep -q "FAILED\|errors"; then
100+
echo "Could not create linked branch via GitHub API."
101+
echo "This may be because the branch needs to be pushed first."
102+
echo "The link will be established when a PR is created."
103+
else
104+
echo "Successfully linked branch '$BRANCH' to issue #$ISSUE_NUMBER"
105+
fi
106+
fi
107+
108+
# Always update local tracking file as a fallback/cache
109+
LOGS_DIR="$(git rev-parse --show-toplevel)/.claude/logs"
110+
mkdir -p "$LOGS_DIR"
111+
TRACKING_FILE="$LOGS_DIR/branch-issues.json"
112+
113+
if [ -f "$TRACKING_FILE" ]; then
114+
# Update existing file
115+
jq --arg branch "$BRANCH" \
116+
--argjson issue "$ISSUE_NUMBER" \
117+
--arg url "https://github.com/$REPO_OWNER/$REPO_NAME/issues/$ISSUE_NUMBER" \
118+
--arg now "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \
119+
'.[$branch] = {issueNumber: $issue, issueUrl: $url, createdAt: $now, linkedViaHook: true}' \
120+
"$TRACKING_FILE" > "$TRACKING_FILE.tmp" && mv "$TRACKING_FILE.tmp" "$TRACKING_FILE"
121+
else
122+
# Create new file
123+
jq -n --arg branch "$BRANCH" \
124+
--argjson issue "$ISSUE_NUMBER" \
125+
--arg url "https://github.com/$REPO_OWNER/$REPO_NAME/issues/$ISSUE_NUMBER" \
126+
--arg now "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \
127+
'{($branch): {issueNumber: $issue, issueUrl: $url, createdAt: $now, linkedViaHook: true}}' \
128+
> "$TRACKING_FILE"
129+
fi
130+
131+
echo "Updated local tracking in $TRACKING_FILE"

.claude/settings.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,18 @@
22
"enabledPlugins": {
33
"github-orchestration@constellos": true,
44
"project-context@constellos": true
5+
},
6+
"hooks": {
7+
"SessionStart": [
8+
{
9+
"hooks": [
10+
{
11+
"type": "command",
12+
"command": ".claude/hooks/link-branch-to-issue.sh ${ISSUE_NUMBER:-}",
13+
"statusMessage": "Linking branch to GitHub issue"
14+
}
15+
]
16+
}
17+
]
518
}
619
}

.github/actions/requirements-reviewer/action.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,32 @@ runs:
5757
ISSUE=""
5858
[[ $BRANCH =~ ^([0-9]+)- ]] && ISSUE="${BASH_REMATCH[1]}"
5959
60-
# Fallback: Parse PR body for "Closes #X", "Fixes #X", "Resolves #X"
60+
# Fallback 1: Parse PR body for "Closes #X", "Fixes #X", "Resolves #X"
6161
if [ -z "$ISSUE" ]; then
6262
PR_BODY=$(gh pr view ${{ inputs.pr_number }} --json body --jq '.body' 2>/dev/null || echo "")
6363
ISSUE=$(echo "$PR_BODY" | grep -oP '(Closes|Fixes|Resolves)\s+#\K[0-9]+' | head -1 || echo "")
6464
fi
6565
66+
# Fallback 2: Query GitHub's closingIssuesReferences API
67+
# This uses GitHub's native issue linking detection
68+
if [ -z "$ISSUE" ]; then
69+
ISSUE=$(gh api graphql -f query='
70+
query($owner: String!, $name: String!, $pr: Int!) {
71+
repository(owner: $owner, name: $name) {
72+
pullRequest(number: $pr) {
73+
closingIssuesReferences(first: 1) {
74+
nodes { number }
75+
}
76+
}
77+
}
78+
}' -f owner='${{ github.repository_owner }}' \
79+
-f name='${{ github.event.repository.name }}' \
80+
-F pr=${{ inputs.pr_number }} \
81+
--jq '.data.repository.pullRequest.closingIssuesReferences.nodes[0].number' 2>/dev/null || echo "")
82+
# Clean up null/empty responses
83+
[ "$ISSUE" = "null" ] && ISSUE=""
84+
fi
85+
6686
echo "issue=$ISSUE" >> $GITHUB_OUTPUT
6787
6888
# Get changed files (gracefully handle permission errors)

0 commit comments

Comments
 (0)