mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Jules agents cannot git push due to sandbox restrictions, which breaks the draft PR-based distributed lock mechanism. Multiple agents can pick up the same task because the claim is never visible remotely. This adds scripts/agent/claim-task-api.sh which creates the remote branch, started.md log file, and draft PR entirely via curl and the GitHub REST API, bypassing git push. The scheduler-flow.md, META_PROMPTS.md, and AGENTS.md are updated to document the fallback: try git push first, fall back to the API script if push is blocked, fail-closed if neither method works. https://claude.ai/code/session_01BpDsVSAkVWtJLnfKxsB9ib
229 lines
7.6 KiB
Bash
Executable File
229 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# claim-task-api.sh — Create a remote branch + started.md + draft PR
|
|
# entirely via GitHub REST API, bypassing 'git push'.
|
|
#
|
|
# This solves the Jules sandbox restriction where git push is blocked
|
|
# but curl/HTTP requests are allowed.
|
|
#
|
|
# Usage:
|
|
# ./scripts/agent/claim-task-api.sh \
|
|
# --agent <agent-name> \
|
|
# --cadence <daily|weekly> \
|
|
# [--base <branch>] \
|
|
# [--run-id <id>]
|
|
#
|
|
# Environment:
|
|
# GITHUB_TOKEN — Required. A token with repo write access.
|
|
# Falls back to GH_TOKEN if GITHUB_TOKEN is unset.
|
|
#
|
|
# Outputs (stdout):
|
|
# CLAIM_BRANCH=<branch-name>
|
|
# CLAIM_PR_NUMBER=<number>
|
|
# CLAIM_PR_URL=<url>
|
|
#
|
|
# Exit codes:
|
|
# 0 — Claim created successfully
|
|
# 1 — Usage error or missing token
|
|
# 2 — API error (branch/file/PR creation failed)
|
|
|
|
OWNER="PR0M3TH3AN"
|
|
REPO="bitvid"
|
|
API_BASE="https://api.github.com/repos/${OWNER}/${REPO}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Parse arguments
|
|
# ---------------------------------------------------------------------------
|
|
AGENT=""
|
|
CADENCE=""
|
|
BASE_BRANCH="unstable"
|
|
RUN_ID=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--agent) AGENT="$2"; shift 2 ;;
|
|
--cadence) CADENCE="$2"; shift 2 ;;
|
|
--base) BASE_BRANCH="$2"; shift 2 ;;
|
|
--run-id) RUN_ID="$2"; shift 2 ;;
|
|
*)
|
|
echo "ERROR: Unknown argument: $1" >&2
|
|
echo "Usage: $0 --agent <name> --cadence <daily|weekly> [--base <branch>] [--run-id <id>]" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$AGENT" || -z "$CADENCE" ]]; then
|
|
echo "ERROR: --agent and --cadence are required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$CADENCE" != "daily" && "$CADENCE" != "weekly" ]]; then
|
|
echo "ERROR: --cadence must be 'daily' or 'weekly'." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Resolve token
|
|
# ---------------------------------------------------------------------------
|
|
TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
|
|
if [[ -z "$TOKEN" ]]; then
|
|
echo "ERROR: GITHUB_TOKEN (or GH_TOKEN) must be set for API access." >&2
|
|
echo "Without a token, remote task locking is impossible." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helper: authenticated GitHub API call
|
|
# ---------------------------------------------------------------------------
|
|
gh_api() {
|
|
local method="$1"
|
|
local endpoint="$2"
|
|
local data="${3:-}"
|
|
|
|
local args=(
|
|
-s -f
|
|
-X "$method"
|
|
-H "Authorization: Bearer ${TOKEN}"
|
|
-H "Accept: application/vnd.github+json"
|
|
-H "X-GitHub-Api-Version: 2022-11-28"
|
|
)
|
|
|
|
if [[ -n "$data" ]]; then
|
|
args+=(-H "Content-Type: application/json" -d "$data")
|
|
fi
|
|
|
|
curl "${args[@]}" "${API_BASE}${endpoint}"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Generate identifiers
|
|
# ---------------------------------------------------------------------------
|
|
TIMESTAMP=$(date -u +"%Y-%m-%d_%H-%M-%S")
|
|
DATE_ONLY=$(date -u +"%Y-%m-%d")
|
|
RUN_ID="${RUN_ID:-$(date -u +"%s")}"
|
|
BRANCH_NAME="agents/${CADENCE}/${AGENT}/${DATE_ONLY}-run-${RUN_ID}"
|
|
LOG_DIR="docs/agents/task-logs/${CADENCE}"
|
|
LOG_FILE="${TIMESTAMP}_${AGENT}_started.md"
|
|
LOG_PATH="${LOG_DIR}/${LOG_FILE}"
|
|
|
|
echo "Claiming task: ${AGENT} (${CADENCE})" >&2
|
|
echo "Branch: ${BRANCH_NAME}" >&2
|
|
echo "Log file: ${LOG_PATH}" >&2
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 1: Get the latest commit SHA of the base branch
|
|
# ---------------------------------------------------------------------------
|
|
echo "Step 1: Fetching base branch SHA (${BASE_BRANCH})..." >&2
|
|
BASE_REF_JSON=$(gh_api GET "/git/refs/heads/${BASE_BRANCH}")
|
|
BASE_SHA=$(echo "$BASE_REF_JSON" | jq -r '.object.sha // empty')
|
|
|
|
if [[ -z "$BASE_SHA" ]]; then
|
|
echo "ERROR: Could not resolve base branch '${BASE_BRANCH}'." >&2
|
|
echo "API response: ${BASE_REF_JSON}" >&2
|
|
exit 2
|
|
fi
|
|
echo " Base SHA: ${BASE_SHA}" >&2
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 2: Create the remote branch
|
|
# ---------------------------------------------------------------------------
|
|
echo "Step 2: Creating remote branch..." >&2
|
|
CREATE_REF_JSON=$(gh_api POST "/git/refs" "$(jq -n \
|
|
--arg ref "refs/heads/${BRANCH_NAME}" \
|
|
--arg sha "$BASE_SHA" \
|
|
'{ref: $ref, sha: $sha}')")
|
|
|
|
CREATED_REF=$(echo "$CREATE_REF_JSON" | jq -r '.ref // empty')
|
|
if [[ -z "$CREATED_REF" ]]; then
|
|
# Check if it's a 422 (already exists) — another agent may have claimed it
|
|
ERROR_MSG=$(echo "$CREATE_REF_JSON" | jq -r '.message // empty')
|
|
if [[ "$ERROR_MSG" == *"Reference already exists"* ]]; then
|
|
echo "RACE DETECTED: Branch '${BRANCH_NAME}' already exists. Another agent claimed first." >&2
|
|
exit 2
|
|
fi
|
|
echo "ERROR: Failed to create branch." >&2
|
|
echo "API response: ${CREATE_REF_JSON}" >&2
|
|
exit 2
|
|
fi
|
|
echo " Created: ${CREATED_REF}" >&2
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 3: Create the started.md log file on the branch
|
|
# ---------------------------------------------------------------------------
|
|
echo "Step 3: Creating started.md log file..." >&2
|
|
|
|
LOG_CONTENT=$(cat <<LOGEOF
|
|
# Started ${AGENT} run
|
|
|
|
Agent: ${AGENT}
|
|
Date: ${DATE_ONLY}
|
|
Branch: ${BRANCH_NAME}
|
|
Claimed-via: GitHub API (claim-task-api.sh)
|
|
LOGEOF
|
|
)
|
|
|
|
# Base64 encode the content (compatible with both GNU and BSD base64)
|
|
ENCODED_CONTENT=$(echo "$LOG_CONTENT" | base64 -w 0 2>/dev/null || echo "$LOG_CONTENT" | base64)
|
|
|
|
CREATE_FILE_JSON=$(gh_api PUT "/contents/${LOG_PATH}" "$(jq -n \
|
|
--arg message "chore(agents): claim ${CADENCE} task for ${AGENT}" \
|
|
--arg content "$ENCODED_CONTENT" \
|
|
--arg branch "$BRANCH_NAME" \
|
|
'{message: $message, content: $content, branch: $branch}')")
|
|
|
|
FILE_PATH_CREATED=$(echo "$CREATE_FILE_JSON" | jq -r '.content.path // empty')
|
|
if [[ -z "$FILE_PATH_CREATED" ]]; then
|
|
echo "ERROR: Failed to create log file." >&2
|
|
echo "API response: ${CREATE_FILE_JSON}" >&2
|
|
# Clean up: delete the branch we created
|
|
gh_api DELETE "/git/refs/heads/${BRANCH_NAME}" >/dev/null 2>&1 || true
|
|
exit 2
|
|
fi
|
|
echo " Created: ${FILE_PATH_CREATED}" >&2
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 4: Create a draft PR
|
|
# ---------------------------------------------------------------------------
|
|
echo "Step 4: Creating draft PR..." >&2
|
|
PR_TITLE="[${CADENCE}] ${AGENT}: ${DATE_ONLY} run"
|
|
PR_BODY="Automated task claim for **${AGENT}** (${CADENCE} cadence).
|
|
|
|
Branch: \`${BRANCH_NAME}\`
|
|
Claimed at: ${TIMESTAMP} UTC
|
|
Method: GitHub API (claim-task-api.sh)
|
|
|
|
---
|
|
*This draft PR serves as a distributed lock. Do not merge until the agent completes its work.*"
|
|
|
|
CREATE_PR_JSON=$(gh_api POST "/pulls" "$(jq -n \
|
|
--arg title "$PR_TITLE" \
|
|
--arg body "$PR_BODY" \
|
|
--arg head "$BRANCH_NAME" \
|
|
--arg base "$BASE_BRANCH" \
|
|
'{title: $title, body: $body, head: $head, base: $base, draft: true}')")
|
|
|
|
PR_NUMBER=$(echo "$CREATE_PR_JSON" | jq -r '.number // empty')
|
|
PR_URL=$(echo "$CREATE_PR_JSON" | jq -r '.html_url // empty')
|
|
|
|
if [[ -z "$PR_NUMBER" ]]; then
|
|
echo "WARNING: Draft PR creation failed. Branch and log file exist as partial lock." >&2
|
|
echo "API response: ${CREATE_PR_JSON}" >&2
|
|
# Don't exit — the branch itself is still a visible claim
|
|
PR_NUMBER="FAILED"
|
|
PR_URL="FAILED"
|
|
else
|
|
echo " PR #${PR_NUMBER}: ${PR_URL}" >&2
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Output machine-readable results
|
|
# ---------------------------------------------------------------------------
|
|
echo "CLAIM_BRANCH=${BRANCH_NAME}"
|
|
echo "CLAIM_PR_NUMBER=${PR_NUMBER}"
|
|
echo "CLAIM_PR_URL=${PR_URL}"
|
|
echo "CLAIM_LOG_FILE=${LOG_PATH}"
|
|
echo ""
|
|
echo "Task claimed successfully." >&2
|