The three .github/workflows files (release.yml, compat-daily.yml, cache-cleanup-weekly.yml) all target the GitHub API: - release.yml uses `gh release create` - compat-daily.yml does `git push https://x-access-token@github.com/...` to a `badges` branch - cache-cleanup-weekly.yml uses `gh cache delete` On Gitea Actions these silently mis-route to GitHub with a Gitea-issued GITHUB_TOKEN, which github.com rejects ("Invalid username or token") — so every scheduled run fails. Drop them on secure-main (upstream main keeps them for the original GitHub repo, untouched) and add .gitea/workflows/release.yml that: - triggers on tag push v* or manual workflow_dispatch - builds release notes from the changelog window since the prior tag - creates the release via Gitea API, or refreshes assets if it already exists (so re-runs are idempotent) - attaches install.sh + install.ps1 so the README's /releases/latest/download/install.{sh,ps1} URLs resolve Tag a release with: git tag v1.0.0 && git push origin v1.0.0
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
name: Release
|
||||
|
||||
# Triggered when a tag matching v* is pushed. Builds a Gitea release with
|
||||
# install.sh + install.ps1 attached as assets, so README's
|
||||
# `releases/latest/download/install.sh` URL resolves.
|
||||
#
|
||||
# Manual trigger also supported: workflow_dispatch with a tag input lets
|
||||
# you re-publish assets onto an existing tag (idempotent — existing
|
||||
# attachments are replaced).
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to publish (e.g. v1.0.0). Tag must already exist.'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve tag
|
||||
id: tag
|
||||
run: |
|
||||
if [ -n "${{ inputs.tag }}" ]; then
|
||||
tag="${{ inputs.tag }}"
|
||||
else
|
||||
tag="${GITHUB_REF_NAME}"
|
||||
fi
|
||||
echo "name=${tag}" >> "$GITHUB_OUTPUT"
|
||||
prev=$(git tag --sort=-version:refname | grep -v "^${tag}$" | head -n1 || true)
|
||||
echo "prev=${prev}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build release notes
|
||||
id: notes
|
||||
run: |
|
||||
tag="${{ steps.tag.outputs.name }}"
|
||||
prev="${{ steps.tag.outputs.prev }}"
|
||||
{
|
||||
echo "## Changes"
|
||||
echo
|
||||
if [ -n "$prev" ]; then
|
||||
git log --pretty='- %s (%h)' "${prev}..${tag}"
|
||||
else
|
||||
git log --pretty='- %s (%h)' "$tag"
|
||||
fi
|
||||
echo
|
||||
echo "## Install"
|
||||
echo
|
||||
echo '**macOS / Linux:**'
|
||||
echo '```bash'
|
||||
echo "curl -fsSL ${{ github.server_url }}/${{ github.repository }}/releases/latest/download/install.sh | bash"
|
||||
echo '```'
|
||||
echo
|
||||
echo '**Windows (PowerShell):**'
|
||||
echo '```powershell'
|
||||
echo "irm ${{ github.server_url }}/${{ github.repository }}/releases/latest/download/install.ps1 | iex"
|
||||
echo '```'
|
||||
} > release-notes.md
|
||||
|
||||
- name: Publish release with assets
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SERVER: ${{ github.server_url }}
|
||||
REPO: ${{ github.repository }}
|
||||
TAG: ${{ steps.tag.outputs.name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
api="${SERVER}/api/v1/repos/${REPO}"
|
||||
notes=$(cat release-notes.md)
|
||||
payload=$(jq -n --arg tag "$TAG" --arg notes "$notes" \
|
||||
'{tag_name: $tag, name: ("ClawGod " + $tag), body: $notes, draft: false, prerelease: false}')
|
||||
|
||||
# Find or create the release for this tag.
|
||||
existing=$(curl -fsSL -H "Authorization: token $TOKEN" \
|
||||
"${api}/releases/tags/${TAG}" 2>/dev/null || echo "")
|
||||
rel_id=$(echo "$existing" | jq -r '.id // empty')
|
||||
|
||||
if [ -z "$rel_id" ]; then
|
||||
rel=$(curl -fsSL -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" \
|
||||
"${api}/releases")
|
||||
rel_id=$(echo "$rel" | jq -r '.id')
|
||||
echo "Created release $TAG (id=$rel_id)"
|
||||
else
|
||||
echo "Release $TAG already exists (id=$rel_id) — refreshing assets only"
|
||||
fi
|
||||
|
||||
# Delete any pre-existing attachments with our names, then upload fresh.
|
||||
assets=$(curl -fsSL -H "Authorization: token $TOKEN" \
|
||||
"${api}/releases/${rel_id}/assets")
|
||||
for name in install.sh install.ps1; do
|
||||
old_id=$(echo "$assets" | jq -r --arg n "$name" '.[] | select(.name==$n) | .id' | head -n1)
|
||||
if [ -n "$old_id" ]; then
|
||||
curl -fsSL -X DELETE \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
"${api}/releases/${rel_id}/assets/${old_id}"
|
||||
echo "Deleted stale asset $name (id=$old_id)"
|
||||
fi
|
||||
curl -fsSL -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Content-Type: multipart/form-data" \
|
||||
-F "attachment=@${name}" \
|
||||
"${api}/releases/${rel_id}/assets?name=${name}" >/dev/null
|
||||
echo "Uploaded $name"
|
||||
done
|
||||
@@ -1,54 +0,0 @@
|
||||
name: Cache Cleanup Weekly
|
||||
|
||||
# compat-daily.yml writes a fresh `~/.npm` cache entry on every successful
|
||||
# run (key includes ${{ github.run_id }}, which is unique per run) and reads
|
||||
# previous entries via restore-keys. GitHub's automatic 7-day cache expiry
|
||||
# only kicks in for entries that haven't been *accessed* — the restore-keys
|
||||
# match counts as access, so daily-tested entries never expire on their
|
||||
# own. Without housekeeping, cache entries accumulate until we hit the
|
||||
# 10 GB per-repo limit and GitHub starts evicting LRU, which sometimes
|
||||
# kicks the entries we actually wanted to keep.
|
||||
#
|
||||
# Run weekly: list every cache, delete it. Next compat-daily run repopulates
|
||||
# `~/.npm` from a fresh npm-registry fetch (one extra ~80 MB download per
|
||||
# week, trivial cost).
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 4 * * 0' # Sunday 04:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
actions: write # required for `gh cache delete`
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
purge:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Delete all repo caches
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# gh cache list paginates; --limit 200 handles up to 200 entries
|
||||
# in a single call (we'd never expect that many anyway).
|
||||
ids=$(gh cache list --repo "$REPO" --limit 200 --json id --jq '.[].id')
|
||||
if [[ -z "$ids" ]]; then
|
||||
echo "No caches to delete."
|
||||
exit 0
|
||||
fi
|
||||
count=$(printf '%s\n' "$ids" | grep -c . || true)
|
||||
echo "Deleting $count cache entries…"
|
||||
while IFS= read -r id; do
|
||||
[[ -z "$id" ]] && continue
|
||||
gh cache delete "$id" --repo "$REPO" \
|
||||
&& echo " ✓ $id" \
|
||||
|| echo " ✗ $id (already gone or transient error)"
|
||||
done <<< "$ids"
|
||||
echo "Done. Next compat-daily run will repopulate ~/.npm fresh."
|
||||
@@ -1,212 +0,0 @@
|
||||
name: Compat Daily
|
||||
|
||||
# Runs install.sh end-to-end on every supported platform against the current
|
||||
# latest Claude Code from npm. Catches the kind of regression we hit when
|
||||
# Anthropic bumps the embedded Bun runtime ahead of Bun's stable release.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 7 * * *' # ~07:17 UTC daily
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- install.sh
|
||||
- .github/workflows/compat-daily.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- install.sh
|
||||
- .github/workflows/compat-daily.yml
|
||||
|
||||
permissions:
|
||||
contents: write # write needed to push the version-badge JSON to `badges` branch
|
||||
issues: write
|
||||
|
||||
# Opt every JS-based action (e.g. actions/checkout, actions/cache,
|
||||
# actions/github-script) onto Node 24, ahead of GitHub forcing it on
|
||||
# 2026-06-02. Silences the deprecation warning and keeps us aligned
|
||||
# with the Node version we use to run patch.mjs / extract-natives.mjs.
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
smoke:
|
||||
# Single Linux runner — a Linux failure almost always means cross-platform
|
||||
# breakage upstream. macOS / Windows runners are 10x / 2x more expensive
|
||||
# than Linux on shared-tier accounting, so we don't burn them on a daily
|
||||
# signal.
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Set up Bun (canary)
|
||||
# Anthropic ships claude-code with bleeding-edge Bun (e.g. 1.3.14
|
||||
# before it leaves the canary channel). Stable Bun panics with
|
||||
# "Expected CommonJS module to have a function wrapper" when loading
|
||||
# cli.original.cjs extracted from a newer-Bun-built native binary.
|
||||
# setup-bun caches the canary binary by commit hash — same canary
|
||||
# build is restored from cache, new build re-downloads.
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: canary
|
||||
|
||||
- name: Cache ~/.npm (claude-code-<plat> tarball)
|
||||
# install.sh runs `npm pack @anthropic-ai/claude-code-linux-x64@latest`,
|
||||
# which lands the ~80 MB tarball in ~/.npm/_cacache. Reusing the cache
|
||||
# lets npm short-circuit the download when @latest hasn't bumped since
|
||||
# the last run; it still re-validates registry metadata, so we never
|
||||
# serve a stale binary on a real upgrade day.
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-claude-${{ runner.os }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
npm-claude-${{ runner.os }}-
|
||||
|
||||
- name: Install ripgrep
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update -qq && sudo apt-get install -y ripgrep
|
||||
rg --version | head -1
|
||||
|
||||
- name: Add ~/.local/bin to PATH (clawgod launcher install target)
|
||||
shell: bash
|
||||
run: echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Print runtime versions
|
||||
shell: bash
|
||||
run: |
|
||||
bun --version
|
||||
node --version
|
||||
uname -a
|
||||
|
||||
- name: Run install.sh (npm-fallback path)
|
||||
# No official Claude binary pre-installed → install.sh exercises
|
||||
# its npm-registry fallback to fetch @anthropic-ai/claude-code-<plat>.
|
||||
# Capture output so the patch-result line can be asserted on; we
|
||||
# don't trust `patch.mjs --verify` because some patches have no
|
||||
# post-state sentinel (they use a regex-stale heuristic that
|
||||
# false-positives once the source survives a partial replacement).
|
||||
shell: bash
|
||||
run: |
|
||||
set -o pipefail
|
||||
bash install.sh 2>&1 | tee /tmp/install.log
|
||||
|
||||
- name: Verify install artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
ls -la "$HOME/.clawgod/" || true
|
||||
for f in cli.cjs cli.original.cjs patch.mjs extract-natives.mjs post-process.mjs .source-version; do
|
||||
test -e "$HOME/.clawgod/$f" || { echo "::error::missing ~/.clawgod/$f"; exit 1; }
|
||||
done
|
||||
echo "Source: $(cat "$HOME/.clawgod/.source-version")"
|
||||
|
||||
- name: Assert all patches applied (parse install log)
|
||||
# patch.mjs prints "Result: A applied, S skipped, F failed".
|
||||
# F must be 0 — otherwise upstream has shifted enough that one of
|
||||
# our regex patches no longer matches and a feature is silently broken.
|
||||
shell: bash
|
||||
run: |
|
||||
line=$(grep -E 'Result: [0-9]+ applied, [0-9]+ skipped, [0-9]+ failed' /tmp/install.log | tail -1 || true)
|
||||
echo "Patch summary: ${line:-<not found>}"
|
||||
[[ -n "$line" ]] || { echo "::error::patch.mjs result line missing from install.sh output"; exit 1; }
|
||||
failed=$(echo "$line" | sed -E 's/.*skipped, ([0-9]+) failed.*/\1/')
|
||||
[[ "$failed" -eq 0 ]] || { echo "::error::patch.mjs reported $failed failed patches"; exit 1; }
|
||||
|
||||
- name: Smoke test — claude --version
|
||||
# Exercises the wrapper path:
|
||||
# launcher → bun cli.cjs → require('./cli.original.cjs')
|
||||
# The Bun CJS-wrapper panic surfaces here, not at install time.
|
||||
shell: bash
|
||||
run: |
|
||||
set +e
|
||||
out=$(claude --version 2>&1)
|
||||
rc=$?
|
||||
set -e
|
||||
echo "$out"
|
||||
if echo "$out" | grep -q "Expected CommonJS module to have a function wrapper"; then
|
||||
echo "::error::Bun CJS-wrapper panic — local Bun lags the embedded Bun"
|
||||
exit 1
|
||||
fi
|
||||
[[ $rc -eq 0 ]] || { echo "::error::claude --version exited $rc"; exit 1; }
|
||||
|
||||
- name: Publish supported-Claude-version badge
|
||||
# Write the version we just verified end-to-end to a JSON file on the
|
||||
# `badges` branch (force-pushed; that branch holds nothing else and is
|
||||
# never read as source). README links to it via shields.io endpoint.
|
||||
# PRs from forks can't push, so skip them.
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
version=$(cat "$HOME/.clawgod/.source-version")
|
||||
[[ -n "$version" ]] || { echo "::error::source-version missing"; exit 1; }
|
||||
tmp=$(mktemp -d)
|
||||
cd "$tmp"
|
||||
cat > claude-version.json <<EOF
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "Claude tested",
|
||||
"message": "$version",
|
||||
"color": "brightgreen"
|
||||
}
|
||||
EOF
|
||||
git init -q -b badges
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config user.name "github-actions[bot]"
|
||||
git add claude-version.json
|
||||
git commit -q -m "compat-daily: claude $version verified"
|
||||
git push -fq "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:badges
|
||||
echo "Published badge: claude $version"
|
||||
|
||||
- name: Open / update issue on scheduled failure
|
||||
if: failure() && github.event_name == 'schedule'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const stamp = path.join(process.env.HOME, '.clawgod', '.source-version');
|
||||
const claudeVersion = fs.existsSync(stamp)
|
||||
? fs.readFileSync(stamp, 'utf8').trim()
|
||||
: 'unknown';
|
||||
const title = `compat-daily: broke (claude ${claudeVersion})`;
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
const { data: open } = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
labels: 'compat-broken',
|
||||
per_page: 100,
|
||||
});
|
||||
const dup = open.find(i => i.title === title);
|
||||
if (dup) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: dup.number,
|
||||
body: `Re-failed ${new Date().toISOString().slice(0,10)} — ${runUrl}`,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title,
|
||||
labels: ['compat-broken', 'bug'],
|
||||
body: [
|
||||
`Daily compatibility run failed on \`ubuntu-latest\`.`,
|
||||
``,
|
||||
`- Claude binary: \`${claudeVersion}\``,
|
||||
`- Run: ${runUrl}`,
|
||||
``,
|
||||
`Auto-opened by \`.github/workflows/compat-daily.yml\`.`,
|
||||
].join('\n'),
|
||||
});
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
# Project policy: never run JS-based actions on Node 20 — they're already
|
||||
# deprecated and forced off after 2026-06-02. Pin every action that runs
|
||||
# JS (actions/checkout etc.) to Node 24 across all our workflows.
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve versions
|
||||
id: versions
|
||||
run: |
|
||||
tag="${GITHUB_REF_NAME}"
|
||||
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
|
||||
prev=$(git tag --sort=-version:refname | grep -v "^${tag}$" | head -n1)
|
||||
echo "prev=${prev}" >> "$GITHUB_OUTPUT"
|
||||
if [ -n "$prev" ]; then
|
||||
echo "range=${prev}..${tag}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "range=${tag}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Build release notes
|
||||
id: notes
|
||||
run: |
|
||||
tag="${{ steps.versions.outputs.tag }}"
|
||||
prev="${{ steps.versions.outputs.prev }}"
|
||||
range="${{ steps.versions.outputs.range }}"
|
||||
{
|
||||
echo "## Changes"
|
||||
echo
|
||||
if [ -n "$prev" ]; then
|
||||
git log --pretty='- %s (%h)' "$range"
|
||||
else
|
||||
git log --pretty='- %s (%h)' "$tag"
|
||||
fi
|
||||
echo
|
||||
echo "## Install"
|
||||
echo
|
||||
echo '**macOS / Linux:**'
|
||||
echo '```bash'
|
||||
echo "curl -fsSL https://github.com/${{ github.repository }}/releases/latest/download/install.sh | bash"
|
||||
echo '```'
|
||||
echo
|
||||
echo '**Windows (PowerShell):**'
|
||||
echo '```powershell'
|
||||
echo "irm https://github.com/${{ github.repository }}/releases/latest/download/install.ps1 | iex"
|
||||
echo '```'
|
||||
if [ -n "$prev" ]; then
|
||||
echo
|
||||
echo "**Full changelog:** https://github.com/${{ github.repository }}/compare/${prev}...${tag}"
|
||||
fi
|
||||
} > release-notes.md
|
||||
|
||||
- name: Create GitHub release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
tag="${{ steps.versions.outputs.tag }}"
|
||||
if gh release view "$tag" >/dev/null 2>&1; then
|
||||
# Release already exists (e.g. created manually with curated notes).
|
||||
# Refresh assets only — don't touch notes/title.
|
||||
gh release upload "$tag" install.sh install.ps1 --clobber
|
||||
else
|
||||
gh release create "$tag" install.sh install.ps1 \
|
||||
--title "ClawGod $tag" \
|
||||
--notes-file release-notes.md
|
||||
fi
|
||||
Reference in New Issue
Block a user