#!/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 \ # --cadence \ # [--base ] \ # [--run-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= # CLAIM_PR_NUMBER= # CLAIM_PR_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 --cadence [--base ] [--run-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="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 </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