Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 163 additions & 40 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
)

runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 2
permissions:
# 📁 Content management (highest permissions)
contents: write
Expand Down Expand Up @@ -279,7 +279,7 @@ jobs:
fi

- name: Debug issue number
run: echo "The issue number is ${{ github.event.number }}"
run: echo "The issue number is ${{ steps.context-info.outputs.issue-number }}"

# Only add comment if it doesn't exist
- name: Find existing status comment
Expand Down Expand Up @@ -443,15 +443,15 @@ jobs:

- name: Run Claude Code
id: claude
uses: davidwells/claude-code-action@main
timeout-minutes: 10
uses: DavidWells/claude-code-action@main
timeout-minutes: 20
continue-on-error: true
with:
use_oauth: true
claude_access_token: ${{ steps.setup-env.outputs.access-token }}
claude_refresh_token: ${{ steps.setup-env.outputs.refresh-token }}
claude_expires_at: ${{ steps.setup-env.outputs.expires-at }}
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
model: ${{ steps.setup-env.outputs.model || 'claude-sonnet-4-20250514' }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# claude_access_token: ${{ secrets.CLAUDE_ACCESS_TOKEN }}
# claude_refresh_token: ${{ secrets.CLAUDE_REFRESH_TOKEN }}
# claude_expires_at: ${{ secrets.CLAUDE_EXPIRES_AT }}
Expand Down Expand Up @@ -794,11 +794,11 @@ jobs:

- name: Check for Changes and Prepare for PR
id: check-changes
if: steps.claude.outcome == 'success' && needs.setup.outputs.is-pr == 'false' && steps.claude.outputs.claude_branch_name
if: steps.claude.outcome == 'success' && needs.setup.outputs.is-pr == 'false' && steps.claude.outputs.branch_name
run: |
set -e # Exit immediately if a command exits with a non-zero status.

BRANCH_NAME="${{ steps.claude.outputs.claude_branch_name }}"
BRANCH_NAME="${{ steps.claude.outputs.branch_name }}"
DEFAULT_BRANCH="origin/${{ github.event.repository.default_branch }}"

echo "--- 1. Checking if remote branch '${BRANCH_NAME}' exists ---"
Expand Down Expand Up @@ -868,14 +868,14 @@ jobs:
if: |
steps.claude.outcome == 'success'
&& needs.setup.outputs.is-pr == 'false'
&& steps.claude.outputs.claude_branch_name
&& steps.claude.outputs.branch_name
&& steps.check-changes.outputs.has-changes == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issueNumber = ${{ needs.setup.outputs.issue-number }}
const branchName = '${{ steps.claude.outputs.claude_branch_name }}'
const branchName = '${{ steps.claude.outputs.branch_name }}'
const defaultBranch = '${{ github.event.repository.default_branch }}'
const owner = context.repo.owner
const repo = context.repo.repo
Expand Down Expand Up @@ -923,24 +923,32 @@ jobs:
repo,
issue_number: issueNumber
})

// Get the last issue comment by claude on issueNumber
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number: issueNumber
})
const claudeComment = comments.find(c => c.user.login === 'claude[bot]' && c.body.includes('Claude'))
const claudeCommentBody = claudeComment ? claudeComment.body.replace(/\[Create PR ➔\]\([^)]+\)/, '') : ''

const prTitle = `🤖 Claude Code: ${issue.title}`
const prBody = `## 🤖 Automated fix by Claude Code

**Related Issue:** #${issueNumber}
**Executed by:** @${{ github.actor }}

### ✅ Next Steps
1. Review the changes in this PR.
2. Confirm that all tests pass.
3. Merge if everything looks good.
---

${claudeCommentBody}

**If additional fixes are needed:** Mention \`@ claude\` in a comment on this PR.

*resolves #${issueNumber}*

---
*Automated PR by [Claude Code Workflow](https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id }})*
*Automated PR by [Claude Code Action - Run #${{ github.run_id }}](https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id }})*
`

const { data: newPr } = await github.rest.pulls.create({
Expand All @@ -955,13 +963,80 @@ jobs:

pr = newPr
console.log(`🎉 PR created successfully: #${pr.number}`)

// Add first comment to the PR for sticky comment
const stickyComment = `
**ℹ️ Action Run:** https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id }}

<!-- claude-run-status -->
`

/* Add sticky comment to the PR for other data */
await github.rest.issues.createComment({
issue_number: pr.number,
owner,
repo,
body: stickyComment,
author: 'github-actions[bot]'
})
}

// 4. Set outputs with the PR info (whether new or existing)
core.setOutput('pr-number', pr.number)
core.setOutput('pr-url', pr.html_url)
core.setOutput('branch-name', branchName)

// Ping our webhook - PR creation successful
try {
const webhookUrl = '${{ secrets.CLAUDE_CODE_NOTIFICATIONS_URL }}';
const webhookToken = '${{ secrets.CLAUDE_CODE_NOTIFICATIONS_KEY }}';

const jobId = `claude-${context.repo.owner}-${context.repo.repo}-${issueNumber}-${Date.now()}`;
const actionUrl = `https://github.com/${owner}/${repo}/issues/${issueNumber}`;

const webhookData = {
jobId: jobId,
status: 'completed',
repository: `${context.repo.owner}/${context.repo.repo}`,
url: actionUrl,
branch: branchName || context.payload.repository?.default_branch || 'main',
commit: '${{ github.sha }}',
results: {
prCreated: true,
prNumber: pr.number,
prUrl: pr.html_url,
issueNumber: issueNumber,
actor: '${{ github.actor }}',
event: '${{ github.event_name }}',
framework: '${{ steps.project-info.outputs.framework }}',
totalFiles: '${{ steps.project-info.outputs.total-files }}',
hasChanges: '${{ steps.check-changes.outputs.has-changes }}',
managementSuccess: '${{ steps.advanced-management.outputs.management-success }}'
}
};

console.log('🔔 Sending webhook notification (PR created)...');
console.log('Webhook URL:', webhookUrl);
console.log('Job ID:', jobId);

const response = await fetch(`${webhookUrl}/job-complete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${webhookToken}`
},
body: JSON.stringify(webhookData)
});

if (response.ok) {
console.log('✅ Webhook notification sent successfully');
} else {
console.warn(`⚠️ Webhook notification failed: ${response.status} ${response.statusText}`);
}
} catch (error) {
console.warn('⚠️ Webhook notification error:', error.message);
}

} catch (error) {
console.error('❌ PR creation/update error:', error)
core.setOutput('error', error.message)
Expand All @@ -973,6 +1048,58 @@ jobs:
repo,
body: failureComment
})

// Ping our webhook - PR creation failed
try {
const webhookUrl = '${{ secrets.CLAUDE_CODE_NOTIFICATIONS_URL }}';
const webhookToken = '${{ secrets.CLAUDE_CODE_NOTIFICATIONS_KEY }}';

const jobId = `claude-${context.repo.owner}-${context.repo.repo}-${issueNumber}-${Date.now()}`;
const actionUrl = `https://github.com/${owner}/${repo}/issues/${issueNumber}`;

const webhookData = {
jobId: jobId,
status: 'failed',
repository: `${context.repo.owner}/${context.repo.repo}`,
url: actionUrl,
branch: branchName || context.payload.repository?.default_branch || 'main',
commit: '${{ github.sha }}',
results: {
prCreated: false,
prNumber: null,
prUrl: null,
issueNumber: issueNumber,
actor: '${{ github.actor }}',
event: '${{ github.event_name }}',
framework: '${{ steps.project-info.outputs.framework }}',
totalFiles: '${{ steps.project-info.outputs.total-files }}',
hasChanges: '${{ steps.check-changes.outputs.has-changes }}',
managementSuccess: '${{ steps.advanced-management.outputs.management-success }}',
error: error.message
}
};

console.log('🔔 Sending webhook notification (PR creation failed)...');
console.log('Webhook URL:', webhookUrl);
console.log('Job ID:', jobId);

const response = await fetch(`${webhookUrl}/job-complete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${webhookToken}`
},
body: JSON.stringify(webhookData)
});

if (response.ok) {
console.log('✅ Webhook notification sent successfully');
} else {
console.warn(`⚠️ Webhook notification failed: ${response.status} ${response.statusText}`);
}
} catch (webhookError) {
console.warn('⚠️ Webhook notification error:', webhookError.message);
}
}

- name: Notify on Success
Expand All @@ -992,7 +1119,9 @@ jobs:
const branchName = '${{ steps.auto-pr.outputs.branch-name }}';
const managementSuccess = '${{ steps.advanced-management.outputs.management-success }}' === 'true';
const managementResults = '${{ steps.advanced-management.outputs.management-results }}';

const owner = context.repo.owner
const repo = context.repo.repo

const eventIcons = {
'pull_request_target': '🔀',
'issue_comment': '💬',
Expand Down Expand Up @@ -1020,6 +1149,7 @@ jobs:
if (isPR) {
message += `**🌿 Branch:** \`${{ needs.setup.outputs.head-ref }}\` → \`${{ needs.setup.outputs.base-ref }}\`\n`;
}
message += `**ℹ️ Action Run:** https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id }}\n`

// Repository management results (summary)
if (managementSuccess && managementResults) {
Expand All @@ -1029,15 +1159,24 @@ jobs:
const hasResults = sections.some(s => results[s] && results[s].length > 0);

if (hasResults) {
message += `\n**🚀 Automated management executed:**\n`;
// Check if there are actually any results to show
let hasDisplayableResults = false;
const displayableResults = [];

sections.forEach(section => {
if (results[section] && results[section].length > 0) {
const summary = results[section].filter(r => r.includes('✅') || r.includes('⚠️')).slice(0, 2);
if (summary.length > 0) {
message += `${summary.map(r => `- ${r}`).join('\n')}\n`;
hasDisplayableResults = true;
displayableResults.push(...summary.map(r => `- ${r}`));
}
}
});

if (hasDisplayableResults) {
message += `\n**🚀 Automated management executed:**\n`;
message += displayableResults.join('\n') + '\n';
}
}
} catch (error) {
// Do not display in case of error
Expand Down Expand Up @@ -1082,19 +1221,12 @@ jobs:
message += `**${category}:**\n`;
cmds.forEach(cmd => message += `- \`claude ${cmd}\`\n`);
message += `\n`;
});
})

message += `🔄 **Rerun**: You can run again anytime with \`claude [your instructions]\``;

/* disabled
await github.rest.issues.createComment({
issue_number: ${{ needs.setup.outputs.issue-number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: message
});
*/
message += `<!-- claude-run-status -->`;
// Add sticky comment identifier
message += `\n<!-- claude-run-status -->`;

// Set as output steps.generate_success_comment.outputs.result
// return message;
Expand Down Expand Up @@ -1149,17 +1281,8 @@ jobs:
message += `---\n`;
message += `🔄 **Rerun**: Please try again with \`claude [specific instructions]\`\n`;
message += `📞 **Support**: If the problem persists, please contact an administrator`;

/*
await github.rest.issues.createComment({
issue_number: ${{ needs.setup.outputs.issue-number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: message
});
*/

message += `<!-- claude-run-status -->`;
message += `\n<!-- claude-run-status -->`;

// Set as output steps.generate_error_comment.outputs.result
// return message;
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ node_modules
package-lock.json
*.log
.netlify

# turbo cache
.turbo
# nx cache
.nx
# IDE stuff
**/.idea

Expand Down
5 changes: 4 additions & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"packages": [
"packages/*"
"packages/*",
"!packages/analytics-core/client",
"!packages/analytics-core/server",
"!packages/analytics-plugin-aws-pinpoint"
],
"version": "independent",
"command": {
Expand Down
18 changes: 11 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
"setup": "pnpm install",
"setup-examples": "node ./scripts/installer.js",
"clean": "rimraf packages/*/dist",
"clean:cache": "turbo run clean && rimraf .turbo",
"reset": "npm run clean && lerna clean --yes",
"test": "pnpm --stream -r run test",
"test:watch": "pnpm --parallel --stream run test:watch",
"watch": "pnpm --stream -r run watch",
"test": "turbo run test",
"test:watch": "turbo run test:watch",
"watch": "turbo run watch",
"watch:core": "npm run watch -- --filter analytics...",
"test:core": "pnpm --filter analytics... --stream run test",
"develop": "lerna run develop --parallel",
"build:core": "lerna run build --scope analytics --scope @analytics/core",
"build": "npm run clean && pnpm run -r build",
"postbuild": "lerna run types",
"build:core": "pnpm run -r --filter analytics-utils build && pnpm run -r --filter @analytics/core build",
"build": "npm run clean && turbo run build --filter='!packages/analytics-core/client' --filter='!packages/analytics-core/server'",
"build:no-cache": "npm run clean && turbo run build --no-cache",
"postbuild": "pnpm run -r types",
"build:dev": "npm run clean && lerna run build:dev",
"docs": "node ./scripts/docs.js && lerna run docs && cd site && npm run sync",
"verifyOrigin": "git remote get-url origin",
Expand Down Expand Up @@ -63,8 +65,10 @@
"sync-rpc": "^1.3.6",
"terser": "^5.10.0",
"tsd-jsdoc": "^2.5.0",
"turbo": "^1.12.4",
"typescript": "^4.3.5",
"uglify-js": "^3.15.0"
"uglify-js": "^3.15.0",
"uvu": "^0.5.6"
},
"funding": [
{
Expand Down
Loading