Skip to content
Draft
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
4 changes: 4 additions & 0 deletions .github/self-heal-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# AUTO-UPDATED
schedule: "0 0 * * *"
rationale: "Bootstrap default schedule (daily at midnight UTC)"
last_updated: "2024-05-21T00:00:00Z"
88 changes: 88 additions & 0 deletions .github/workflows/compute-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Compute Self-Heal Schedule

on:
schedule:
- cron: '0 0 * * 0' # Weekly run on Sunday
workflow_dispatch:

permissions:
contents: write
pull-requests: write
actions: read

concurrency:
group: selfheal-schedule-${{ github.ref }}
cancel-in-progress: true

jobs:
compute-schedule:
if: !startsWith(github.ref_name, 'selfheal-schedule-')
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout codebase
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install Dependencies
run: npm ci

- name: Compute New Schedule
id: compute
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/compute_schedule.mjs > compute_schedule.log 2>&1 || true
cat compute_schedule.log

if [ -z "$(git status --porcelain)" ]; then
echo "No changes in schedule."
echo "has_diff=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "has_diff=true" >> $GITHUB_OUTPUT

- name: Guard against duplicate PRs
id: check_pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "${{ steps.compute.outputs.has_diff }}" != "true" ]; then
exit 0
fi

open_prs=$(gh pr list --label self-heal-schedule --state open --json number -q '.[].number')
if [ -n "$open_prs" ]; then
echo "An open self-heal-schedule PR already exists. Skipping PR creation."
echo "create_pr=false" >> $GITHUB_OUTPUT
else
echo "create_pr=true" >> $GITHUB_OUTPUT
fi

- name: Create Pull Request
if: steps.compute.outputs.has_diff == 'true' && steps.check_pr.outputs.create_pr == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH_NAME="selfheal-schedule-$(date +%s)"
git checkout -b "$BRANCH_NAME"

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

git commit -a -m "Update self-heal schedule cadence based on telemetry"
git push origin "$BRANCH_NAME"

gh pr create \
--title "[Self-Heal Schedule] Update cadence" \
--body "Automated PR to update the self-heal schedule based on telemetry." \
--label "self-heal-schedule" \
--label "automation"
131 changes: 131 additions & 0 deletions .github/workflows/self-heal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
name: Self-Heal Auto-Repair

on:
schedule:
# AUTO-UPDATED - Do not manually modify this line; use compute_schedule logic or self-heal-schedule.yml
- cron: '0 0 * * *'
workflow_run:
workflows: ["ci"]
types:
- completed
workflow_dispatch:

permissions:
contents: write
pull-requests: write
actions: read

concurrency:
group: selfheal-${{ github.ref }}
cancel-in-progress: true

jobs:
self-heal:
if: >
!startsWith(github.ref_name, 'selfheal-') &&
(
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'failure')
)
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout codebase
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install Dependencies
run: npm ci

- name: Pre-healthcheck (Optional recording state)
id: pre
run: |
node scripts/healthcheck.mjs > pre_healthcheck.log 2>&1 || true
cat pre_healthcheck.log

- name: Run Self-Heal Pipeline
id: repair
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/self_heal.mjs > repair.log 2>&1 || echo "No fix found or fully healthy"
cat repair.log

- name: Post-healthcheck
id: post
run: |
node scripts/healthcheck.mjs > post_healthcheck.log 2>&1
cat post_healthcheck.log

- name: Verify meaningful diff and no secrets
id: check_diff
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "No changes made by self-heal."
echo "has_diff=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "has_diff=true" >> $GITHUB_OUTPUT

git add -A
git reset HEAD .github/workflows/ci.yml .env secrets/ src/**/migrations/ 2>/dev/null || true

if git diff --cached -G"(?i)(password|secret|api[_-]?key|token)" --name-only | grep -q .; then
echo "Potential secrets found in diff!"
exit 1
fi

- name: Guard against duplicate PRs
id: check_pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "${{ steps.check_diff.outputs.has_diff }}" != "true" ]; then
exit 0
fi

open_prs=$(gh pr list --label self-heal --state open --json number -q '.[].number')
if [ -n "$open_prs" ]; then
echo "An open self-heal PR already exists. Skipping PR creation."
echo "create_pr=false" >> $GITHUB_OUTPUT
else
echo "create_pr=true" >> $GITHUB_OUTPUT
fi

- name: Create Pull Request
if: steps.check_diff.outputs.has_diff == 'true' && steps.check_pr.outputs.create_pr == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH_NAME="selfheal-$(date +%s)"
git checkout -b "$BRANCH_NAME"

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

git commit -m "Self-heal auto-repair"
git push origin "$BRANCH_NAME"

TITLE="[Self-Heal] Reactive/Scheduled Repair"
if [ "${{ github.event_name }}" == "schedule" ]; then
TITLE="[Self-Heal Scheduled] Drift fixes"
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
TITLE="[Self-Heal Manual] Repair"
elif [ "${{ github.event_name }}" == "workflow_run" ]; then
TITLE="[Self-Heal Reactive] CI fix"
fi

gh pr create \
--title "$TITLE" \
--body "Automated self-heal repair triggered by ${{ github.event_name }}. See workflow logs for details. (Generated by Claude Code / Jules coding agent)" \
--label "self-heal" \
--label "automation"
39 changes: 39 additions & 0 deletions SELF_HEAL_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Self-Heal Auto-Repair Automation

This project uses an automated self-healing CI pipeline configured via GitHub Actions.

## Triggers

1. **Scheduled:** Proactively runs to detect and repair drift. The schedule is dynamically computed based on the repository's activity level.
2. **CI Failure (Reactive):** Triggers when the main `ci` workflow fails. It attempts to repair the build and opens a PR if it succeeds.
3. **Manual Dispatch:** Can be run on demand from the Actions tab.

## The Repair Pipeline (Idempotent)

The `scripts/self_heal.mjs` script performs the following idempotent steps to resolve issues:
1. **Clean Install:** Runs `npm ci` to rebuild tools and lock dependencies to the lockfile.
2. **Lint/Format:** Uses `eslint --fix` and `prettier -w` to automatically format the code.
3. **Test Snapshots:** Runs `vitest -u` to update any failing test snapshots.
4. **Clean Rebuild:** Wipes out previous TypeScript typestubs (e.g., `tsbuildinfo`) and does a clean build to clear out cache issues.
5. **Dependency Re-resolve:** Refreshes lockfiles cleanly if needed.
6. **Static Build:** Re-runs the final build scripts to regenerate static assets.

After each step, it runs a healthcheck (`scripts/healthcheck.mjs`). If the healthcheck passes and there's a git diff, it stops immediately, exits successfully, and the GitHub Action creates a PR with the fixes.

## Schedule Dynamics

The schedule is controlled by `.github/self-heal-schedule.yml` and computed by `.github/workflows/compute-schedule.yml` (using `scripts/compute_schedule.mjs`). It reads telemetry via the GitHub API (`gh pr list`) and scales the run frequency based on PR velocity (e.g., High velocity = runs every 6 hours; Low velocity = runs weekly).

## How to Override the Schedule

To manually override the schedule:
1. Edit `.github/self-heal-schedule.yml` directly.
2. Ensure the first line is exactly `# AUTO-UPDATED`.
3. The next compute cycle will see your changes. If you wish to freeze it completely, disable the `compute-schedule.yml` workflow.

## Reviewer Checklist

When reviewing a PR from this automation, check:
- [ ] Has it unintentionally altered test assertions or logic? (Only snapshots and formatting are expected).
- [ ] Has it modified restricted files like `.github/workflows/ci.yml` or leaked secrets? (It shouldn't, there are gates preventing it).
- [ ] Is it a legitimate repair of code drift or an unexpected artifact change?
Loading