diff --git a/.env.example b/.env.example index 3f3a523..d35bff2 100644 --- a/.env.example +++ b/.env.example @@ -22,27 +22,29 @@ CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-vervang-mij # Als deze ge-revoked wordt: rebuild + redeploy (zie README). SCRUM4ME_TOKEN=vervang-mij -# ----- GitHub credentials ----------------------------------- -# Personal Access Token (fine-grained) met: -# - Repository access op madhura68/Scrum4Me + madhura68/scrum4me-mcp -# - Permissions: Contents (read/write), Pull requests (read/write), -# Metadata (read) +# ----- Forgejo credentials (PBI-86 hybride model) ----------- +# Personal Access Token van je Forgejo-account met scope read+write +# op de Scrum4Me-repos. Variabele heet historisch nog `GH_TOKEN`; +# in het hybride model bevat 'ie een Forgejo-PAT. # # Gebruikt voor: # 1. Pre-clone van de repos in /home/agent/Projects// bij # container-start (entrypoint.sh) -# 2. `git push` van agent feature-branches via HTTPS -# 3. `gh pr create` (auto_pr=true) — gh CLI leest GH_TOKEN uit env +# 2. `git push` van agent feature-branches naar Forgejo via HTTPS # -# Genereer op github.com → Settings → Developer settings → -# Personal access tokens → Fine-grained tokens. -GH_TOKEN=ghp_vervang-mij +# `gh pr create` is uit de worker-flow verwijderd (PBI-86, T-1005); +# de GitHub-PR ontstaat via de handmatig getriggerde promote-Action +# in Forgejo. +# +# Genereer in Forgejo: avatar → Settings → Applications → +# Generate New Token; scope minimaal `write:repository`. +GH_TOKEN=vervang-mij # Lijst (komma-gescheiden) van repos om vooraf te clonen naar # ~agent/Projects/. resolveRepoRoot in scrum4me-mcp valt -# automatisch terug op die conventie. Voeg meer toe als je nieuwe -# producten/repos toevoegt aan Scrum4Me. -GH_PRECLONE_REPOS=madhura68/Scrum4Me,madhura68/scrum4me-mcp +# automatisch terug op die conventie. `/` zoals 'ie op +# Forgejo staat. Voeg meer toe als je nieuwe producten/repos toevoegt. +GH_PRECLONE_REPOS=janpeter/Scrum4Me,janpeter/scrum4me-mcp # ----- Git commit-author ------------------------------------- # Verplicht — Vercel weigert deploys waarvan de commit-author email @@ -108,3 +110,9 @@ AGENT_BACKOFF_MAX=300 AGENT_LOG_GZIP_AFTER_HOURS=24 # Hoeveel dagen ge-gzipte logs bewaren voor we ze verwijderen. AGENT_LOG_DELETE_AFTER_DAYS=30 + +# Claude CLI --output-format. Default 'stream-json' streamt de volledige +# event-stream (tool-calls, berichten) live naar de run-log; 'text' geeft +# alleen Claude's eind-samenvatting (terser, maar geen live-meekijken). +# stream-json maakt de run-log JSONL — gebruik jq of een viewer. +AGENT_CLAUDE_OUTPUT_FORMAT=stream-json diff --git a/Dockerfile b/Dockerfile index 98fe2b2..683202d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,7 +53,7 @@ RUN curl -fsSL https://claude.ai/install.sh | bash -s ${CLAUDE_CODE_VERSION} \ # Clone zonder submodules — de Prisma-schema zit al gecommit in het repo. # De vendor/scrum4me submodule is alleen nodig om het schema te updaten, # niet om te builden. Pin via build-arg; default = main. -ARG MCP_GIT_REPO=https://github.com/madhura68/scrum4me-mcp.git +ARG MCP_GIT_REPO=https://git.jp-visser.nl/janpeter/scrum4me-mcp.git ARG MCP_GIT_REF=main # Cache-bust voor de clone-laag: hetzelfde MCP_GIT_REF kan tussen rebuilds # een ander commit aanwijzen (bv. main na een merge). Geef als build-arg diff --git a/README.md b/README.md index 825abe7..0f9cd3d 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ fouten. `/share/Agent → /share/CACHEDEV1_DATA/Agent`. - Drie subdirs onder die share: `/share/Agent/cache`, `/share/Agent/logs`, `/share/Agent/state`. Aanmaken via File Station of via SSH na share-creatie. -- Internet-uitgang naar `api.anthropic.com`, `github.com`, je Neon-host, `registry.npmjs.org`. +- Internet-uitgang naar `api.anthropic.com`, `git.jp-visser.nl` (Forgejo HTTPS-clone/push), `cli.github.com` (build-time voor de gh CLI), je Neon-host, `registry.npmjs.org`. > **Verifieer** vóór je deployt dat `/share/Agent` echt op disk staat: > ```bash @@ -85,18 +85,21 @@ fouten. # b. SCRUM4ME_TOKEN → log in als de dedicated agent-user in # Scrum4Me, /settings/tokens, label "NAS-runner" # c. DATABASE_URL/DIRECT_URL → Neon dashboard -# d. GH_TOKEN → github.com → Settings → Developer settings → -# Personal access tokens → Fine-grained. -# Repository access op madhura68/Scrum4Me + -# madhura68/scrum4me-mcp; Permissions: -# Contents (RW), Pull requests (RW), -# Metadata (R). Wordt gebruikt voor clone, -# push en `gh pr create` (auto_pr). +# d. GH_TOKEN → Forgejo → avatar → Settings → +# Applications → Generate New Token; scope +# minimaal `write:repository` op de twee +# repos (janpeter/Scrum4Me + janpeter/ +# scrum4me-mcp). Wordt gebruikt voor clone +# en push naar Forgejo. PBI-86 (hybride +# model): `gh pr create` is uit de +# worker-flow verwijderd — de GitHub-PR +# komt via de handmatige promote-Action +# in Forgejo. # 2. Repo op de NAS plaatsen ssh admin@nas cd /share/Agent -git clone https://github.com//scrum4me-agent-runner.git +git clone https://git.jp-visser.nl//scrum4me-agent-runner.git cd scrum4me-agent-runner # 3. Env aanmaken @@ -326,7 +329,8 @@ Bij elke container-start runt `bin/repo-bootstrap.sh` (als de `agent`-user, ná drop-privileges) en zet zo'n setup neer: 1. Configureert git's credential-helper met `GH_TOKEN` zodat - `git clone`/`push` naar `https://github.com/...` zonder prompt werkt. + `git clone`/`push` naar `https://git.jp-visser.nl/...` (Forgejo) zonder + prompt werkt. 2. Voor elke repo in `GH_PRECLONE_REPOS` (komma-gescheiden owner/name): - Bestaat `~/Projects//.git` al? → `git fetch origin --prune` - Anders → fresh `git clone` @@ -337,8 +341,10 @@ voor jobs landen vervolgens onder `~/.scrum4me-agent-worktrees//` zodat de hoofd-clone niet wordt aangeraakt. Push gaat over dezelfde token: `git push -u origin feat/story-` -slaagt zonder prompt. `gh pr create` (voor producten met `auto_pr=true`) -gebruikt dezelfde `GH_TOKEN` via de `gh` CLI's standaard env-detect. +slaagt zonder prompt. **`gh pr create` is in PBI-86 (T-1005) verwijderd +uit de worker-flow** — de GitHub-PR ontstaat via een handmatig +getriggerde promote-Action in Forgejo (zie de Scrum4Me-repo +`docs/runbooks/forgejo-hybrid-flow.md`). ## Veelvoorkomende issues diff --git a/bin/job-prepare.sh b/bin/job-prepare.sh index 96f54c4..f3ec175 100644 --- a/bin/job-prepare.sh +++ b/bin/job-prepare.sh @@ -28,7 +28,7 @@ if [[ -z "$JOB_ID" || -z "$REPO_URL" ]]; then exit 2 fi -# Slug uit repo_url voor de cache-naam: "github.com/foo/bar.git" → "foo_bar" +# Slug uit repo_url voor de cache-naam: "git.jp-visser.nl/foo/bar.git" → "foo_bar" SLUG=$(echo "$REPO_URL" \ | sed -E 's#^.*[:/]([^/]+/[^/]+?)(\.git)?/?$#\1#' \ | tr '/' '_') diff --git a/bin/repo-bootstrap.sh b/bin/repo-bootstrap.sh index 850c42f..7f71c53 100644 --- a/bin/repo-bootstrap.sh +++ b/bin/repo-bootstrap.sh @@ -32,8 +32,8 @@ fi mkdir -p "$HOME" git config --global credential.helper store CREDS_FILE="$HOME/.git-credentials" -if [[ ! -f "$CREDS_FILE" ]] || ! grep -q "oauth2:${GH_TOKEN}@github.com" "$CREDS_FILE" 2>/dev/null; then - printf 'https://oauth2:%s@github.com\n' "$GH_TOKEN" > "$CREDS_FILE" +if [[ ! -f "$CREDS_FILE" ]] || ! grep -q "oauth2:${GH_TOKEN}@git.jp-visser.nl" "$CREDS_FILE" 2>/dev/null; then + printf 'https://oauth2:%s@git.jp-visser.nl\n' "$GH_TOKEN" > "$CREDS_FILE" chmod 600 "$CREDS_FILE" log "git credentials helper configured at ${CREDS_FILE}" fi @@ -71,7 +71,7 @@ for repo in "${REPOS[@]}"; do else log "cloning ${repo} into ${target}" rm -rf "$target" - git clone --quiet "https://github.com/${repo}.git" "$target" \ + git clone --quiet "https://git.jp-visser.nl/${repo}.git" "$target" \ || { log "ERROR: clone failed for ${repo}"; continue; } fi done diff --git a/bin/run-agent.sh b/bin/run-agent.sh index 52c6f49..6b3dd32 100644 --- a/bin/run-agent.sh +++ b/bin/run-agent.sh @@ -80,8 +80,12 @@ while true; do # Token-expiry detectie: run-one-job.ts retourneert exit 3 wanneer het # bekende auth-error-strings in Claude's output ziet. We checken óók de # log-tekst voor het geval een ander pad het patroon raakt (bv. Prisma- - # connection-error met OAuth-expired in error-body). - if [[ "$exit_code" -eq 3 ]] || grep -qE '(invalid_api_key|authentication.*failed|401.*unauthor|OAuth.*expired)' "${run_log}"; then + # connection-error met OAuth-expired in error-body) — maar alléén bij een + # niet-nul exit. Het run-log bevat de volledige stream-json output (incl. + # tool-results én run-one-job's eigen "TOKEN_EXPIRED detected"-logregel), + # dus een geslaagde job die toevallig "401 unauthorized" in z'n output + # heeft mag de grep-fallback niet triggeren. + if [[ "$exit_code" -eq 3 ]] || { [[ "$exit_code" -ne 0 ]] && grep -qE '(invalid_api_key|authentication.*failed|401.*unauthor|OAuth.*expired)' "${run_log}"; }; then log "AUTH FAILURE detected (exit=$exit_code or pattern in log) — marking TOKEN_EXPIRED" touch "${AGENT_STATE_DIR}/TOKEN_EXPIRED" write_state "$(jq -n \ diff --git a/bin/run-one-job.ts b/bin/run-one-job.ts index cd0919c..f9cc879 100644 --- a/bin/run-one-job.ts +++ b/bin/run-one-job.ts @@ -292,6 +292,13 @@ async function main(): Promise { // 7. Build CLI args. const promptText = getKindPromptText(ctx.kind).replace('$PAYLOAD_PATH', payloadPath) + // --output-format is configureerbaar via env. Default 'stream-json' geeft + // de volledige event-stream (elke tool-call, elk bericht) live in de + // run-log, i.p.v. alleen Claude's eind-samenvatting. stream-json vereist + // --verbose in print-mode. Zet AGENT_CLAUDE_OUTPUT_FORMAT=text terug voor + // de oude terse output. TOKEN_EXPIRED-detectie werkt ongewijzigd: de + // auth-error-strings staan ook binnen de JSON-events. + const outputFormat = process.env.AGENT_CLAUDE_OUTPUT_FORMAT ?? 'stream-json' const args: string[] = [ '-p', promptText, @@ -306,8 +313,9 @@ async function main(): Promise { '--add-dir', '/opt/agent', '--output-format', - 'text', + outputFormat, ] + if (outputFormat === 'stream-json') args.push('--verbose') if (effort) args.push('--effort', effort) const cwd = worktreePath ?? '/opt/agent' @@ -375,13 +383,21 @@ async function main(): Promise { `duration_ms=${durationMs} wall_clock_seconds=${Math.round(durationMs / 1000)}`, ) - // 10. Token-expiry detection. + // 10. Token-expiry detection — alleen als Claude zelf non-zero eindigde. + // stdoutBuf bevat de volledige stream-json output incl. álle tool-results, + // dus de auth-error-strings kunnen ook agent-werk-content zijn (een doc + // over 401-handling gelezen, een endpoint getest). Een echte credential- + // fout laat 'claude' non-zero exiten; een geslaagde run (exit 0) is per + // definitie geen token-expiry. Zonder deze gate legt zulke content de + // worker onterecht plat (run-agent.sh → TOKEN_EXPIRED marker + sleep). let tokenExpired = false - for (const pat of TOKEN_EXPIRY_PATTERNS) { - if (pat.test(stdoutBuf)) { - tokenExpired = true - log(`TOKEN_EXPIRED detected pattern="${pat.source}" exiting code=3`) - break + if (exitCode !== 0) { + for (const pat of TOKEN_EXPIRY_PATTERNS) { + if (pat.test(stdoutBuf)) { + tokenExpired = true + log(`TOKEN_EXPIRED detected pattern="${pat.source}" exiting code=3`) + break + } } }