Lost terugkerende pijn op: na cross-build op Mac vergeet je makkelijk de .env mee te nemen of vanuit de juiste directory te starten, met "FAIL: ... is not set" als gevolg in pre-flight. Script doet in volgorde: 1. docker buildx build --platform linux/amd64 --load 2. docker save | gzip → scrum4me-agent-runner-amd64.tar.gz 3. scp tarball + compose + (eerste keer) .env naar NAS 4. ssh: docker load + sanity-check op .env + compose up --force-recreate 5. ssh: docker compose logs -f (Ctrl-C om te stoppen) Bestaande NAS-.env wordt niet overschreven. Eerste deploy patcht de NAS-paden via sed. Sanity-check faalt expliciet als anthropic-, SCRUM4ME_- of DATABASE_URL-vars ontbreken — ipv stille pre-flight-fail. Config via .env.deploy (zit in .gitignore). Voor eerste deploy en volledige procedure: README "Deploy — cross-build" sectie. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
464 lines
18 KiB
Markdown
464 lines
18 KiB
Markdown
# scrum4me-agent-runner
|
||
|
||
Headless Claude Code worker die de Scrum4Me job-queue (M13) leegtrekt vanaf
|
||
een QNAP NAS via Container Station. Geen Vercel, geen browser, geen
|
||
toetsenbord — Claude Code draait als daemon, claimt jobs uit
|
||
`mcp__scrum4me__wait_for_job`, voert ze uit in een per-job clone, en pusht
|
||
nooit zelf.
|
||
|
||
## Architectuur in één plaatje
|
||
|
||
```
|
||
┌─ QNAP TS-664 (Container Station) ─────────────────────────────┐
|
||
│ │
|
||
│ ┌─ container: agent-runner ────────────────────────────────┐ │
|
||
│ │ PID 1: tini → run-agent.sh (daemon-loop) │ │
|
||
│ │ ├─ health-server.js (8080 → host 18080) │ │
|
||
│ │ └─ claude -p (per-batch, met MCP via stdio) │ │
|
||
│ │ └─ scrum4me-mcp → Neon Postgres │ │
|
||
│ │ │ │
|
||
│ │ /tmp/job-<id> ephemeral working trees │ │
|
||
│ │ /var/cache/repos bare git mirrors (volume) │ │
|
||
│ │ /var/cache/npm npm cache (volume) │ │
|
||
│ │ /var/log/agent run + job logs (volume) │ │
|
||
│ └──────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ /share/Agent/cache /share/Agent/logs /share/Agent/state │
|
||
└────────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼ HTTPS
|
||
Neon Postgres (Scrum4Me DB)
|
||
▲
|
||
│
|
||
Vercel ─── Scrum4Me UI (gebruikers enqueueen jobs)
|
||
```
|
||
|
||
Eén `claude -p`-invocation roept intern `wait_for_job` aan totdat de
|
||
queue leeg is (≈600 s lege block-time → afsluiten). De wrapper start
|
||
`claude -p` opnieuw zodra hij eindigt, met exponentiële backoff bij
|
||
fouten.
|
||
|
||
## Wat zit waar
|
||
|
||
| Bestand | Doel |
|
||
|--------------------------|-----------------------------------------------------------------|
|
||
| `Dockerfile` | Ubuntu 22.04 + Node 22 + Claude Code + scrum4me-mcp + scripts |
|
||
| `docker-compose.yml` | Service-definitie, volumes, env-file, restart-policy, limits |
|
||
| `package.json` | Npm-dependencies van de runner zelf (alleen `scrum4me-mcp` pin) |
|
||
| `mcp-config.json` | Claude Code MCP-config (verwijst stdio naar scrum4me-mcp) |
|
||
| `CLAUDE.md` | Agent-rol-instructies, auto-geladen door `claude -p` |
|
||
| `bin/entrypoint.sh` | Container-startup: dirs, health-server, daemon-loop |
|
||
| `bin/run-agent.sh` | Daemon-loop met backoff, exit-code-routing en state-writes |
|
||
| `bin/check-tokens.sh` | Pre-flight: API-token, OAuth-token, DB-bereikbaarheid |
|
||
| `bin/job-prepare.sh` | Per-job: bare-fetch + clone-via-reference naar `/tmp/job-<id>` |
|
||
| `bin/job-cleanup.sh` | Per-job: logs naar `/var/log`, working tree weg |
|
||
| `bin/health-server.js` | HTTP-endpoint op 8080 (intern) dat state.json en marker-files leest |
|
||
| `bin/rotate-logs.sh` | Compress/cleanup van oude `.log`-bestanden |
|
||
| `.env.example` | Alle env-vars met uitleg |
|
||
|
||
## Vereisten op de NAS
|
||
|
||
- Container Station 2+ (Docker compose v2)
|
||
- **`Agent` als QTS Shared Folder** op een echte volume (bv. `CACHEDEV1_DATA`).
|
||
Niet een `mkdir /share/Agent` — `/share` zelf is een 16 MB tmpfs en handmatige
|
||
directories overleven geen reboot. Aanmaken via Control Panel → Privilege →
|
||
Shared Folders → Create. QTS legt dan automatisch de symlink
|
||
`/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`.
|
||
|
||
> **Verifieer** vóór je deployt dat `/share/Agent` echt op disk staat:
|
||
> ```bash
|
||
> ssh admin@<nas> 'ls -la /share/ | grep Agent; df -h /share/Agent'
|
||
> ```
|
||
> Verwacht een symlink (`l...Agent -> /share/CACHEDEV1_DATA/Agent`) en een
|
||
> df-uitvoer met TB-grootte op `cachedev1`/`cachedev2`. Als je hier `tmpfs 16M`
|
||
> ziet, is de share geen geregistreerde QTS Shared Folder en zal elke transfer
|
||
> >16 MB falen met `scp: write remote ... Failure`.
|
||
|
||
## Deploy
|
||
|
||
```bash
|
||
# 1. Op je werkstation: token's regelen
|
||
# a. CLAUDE_CODE_OAUTH_TOKEN → draai `claude setup-token` (browser-flow)
|
||
# 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).
|
||
|
||
# 2. Repo op de NAS plaatsen
|
||
ssh admin@nas
|
||
cd /share/Agent
|
||
git clone https://github.com/<jij>/scrum4me-agent-runner.git
|
||
cd scrum4me-agent-runner
|
||
|
||
# 3. Env aanmaken
|
||
cp .env.example .env
|
||
chmod 600 .env
|
||
vi .env # vul alle waarden in
|
||
|
||
# 4. Build + start
|
||
docker compose build
|
||
docker compose up -d
|
||
|
||
# 5. Verifiëren
|
||
curl http://nas.local:18080/health
|
||
docker compose logs -f
|
||
```
|
||
|
||
> **QNAP-port:** host-poort 8080 is bezet door de QTS-webinterface; daarom
|
||
> mapt deze stack standaard `18080:8080`. Override via
|
||
> `AGENT_HEALTH_PORT_HOST` in `.env` als je een andere host-poort wilt.
|
||
|
||
## Snelle redeploy — `bin/deploy-to-nas.sh`
|
||
|
||
Voor een **bestaande deploy** die je opnieuw wil bouwen + deployen
|
||
(bijvoorbeeld na een merge in `scrum4me-mcp` of een aanpassing aan
|
||
`CLAUDE.md`):
|
||
|
||
```bash
|
||
# Eenmalig: NAS-target instellen
|
||
cp .env.deploy.example .env.deploy
|
||
vi .env.deploy # zet NAS_HOST=admin@<nas>
|
||
|
||
# Daarna: één commando voor de hele cyclus
|
||
bin/deploy-to-nas.sh
|
||
```
|
||
|
||
Het script doet:
|
||
|
||
1. `docker buildx build --platform linux/amd64 --load`
|
||
2. `docker save | gzip → scrum4me-agent-runner-amd64.tar.gz`
|
||
3. `scp` van tarball + `docker-compose.yml` + (eerste keer) `.env` naar NAS
|
||
4. `ssh` op NAS: `docker load` + sanity-check op `.env` + `docker compose up -d --force-recreate`
|
||
5. `docker compose logs -f` — lokaal-volgbaar terwijl pre-flight + eerste batch starten
|
||
|
||
`.env` op de NAS wordt **niet** overschreven als 'ie er al staat. Bij een
|
||
verse NAS-installatie wordt 'ie wél geüpload + ge-sed't (NAS_BASE,
|
||
AGENT_UID, etc.). Voor de **eerste deploy** of een schoon volume zie de
|
||
volledige procedure hieronder.
|
||
|
||
## Deploy — cross-build vanaf Mac (Apple Silicon → amd64-NAS)
|
||
|
||
Alternatief voor de in-place build hierboven. Bouw de image op je Mac voor
|
||
`linux/amd64`, schrijf 'm naar een tarball, transfer naar de NAS en laad daar.
|
||
Handig als de NAS te langzaam is om te builden (npm install op een N5095 met
|
||
NAS-storage is traag) of als je geen `git push` wilt voor elke iteratie.
|
||
|
||
**Vóór je begint:** controleer dat `/share/Agent` een echte QTS Shared Folder is
|
||
(zie [Vereisten op de NAS](#vereisten-op-de-nas)). Dat is de meest voorkomende
|
||
val.
|
||
|
||
### 1. Tokens en `.env` op je Mac
|
||
|
||
Zelfde tokens als in [Deploy](#deploy). Hou er rekening mee dat je **twee
|
||
`.env`-bestanden** kunt willen: één voor lokaal Mac-testen
|
||
(`AGENT_PLATFORM=linux/arm64`, `AGENT_UID=501`, paths onder `/Users/...`) en
|
||
één voor NAS-runtime. De NAS-versie wordt bij stap 4 ge-scp'd en met `sed`
|
||
geschikt gemaakt.
|
||
|
||
### 2. Image bouwen voor amd64
|
||
|
||
```bash
|
||
cd /Users/<jij>/Development/scrum4me-docker
|
||
|
||
docker buildx build \
|
||
--platform linux/amd64 \
|
||
--build-arg MCP_GIT_REF=main \
|
||
--build-arg CLAUDE_CODE_VERSION=latest \
|
||
--build-arg AGENT_UID=1000 \
|
||
--build-arg AGENT_GID=1000 \
|
||
-t scrum4me-agent-runner:local \
|
||
--load \
|
||
.
|
||
```
|
||
|
||
`AGENT_UID/GID=1000` zijn de **NAS-UIDs**, niet je Mac-UIDs (vaak `501/20`).
|
||
Bij verkeerde UIDs kan de container niet schrijven naar de bind-mounts.
|
||
|
||
Verifieer architectuur:
|
||
|
||
```bash
|
||
docker image inspect scrum4me-agent-runner:local --format '{{.Architecture}}'
|
||
# verwacht: amd64
|
||
```
|
||
|
||
### 3. Image naar tarball
|
||
|
||
```bash
|
||
docker save scrum4me-agent-runner:local | gzip > scrum4me-agent-runner-amd64.tar.gz
|
||
shasum -a 256 scrum4me-agent-runner-amd64.tar.gz
|
||
```
|
||
|
||
De tarball is ~580 MB voor de huidige image-size. Hij staat in `.gitignore`,
|
||
dus geen risico op accidenteel committen.
|
||
|
||
### 4. NAS voorbereiden + bestanden transferren
|
||
|
||
```bash
|
||
# .env naar de NAS (de Mac-versie; we patchen path-velden zo direct)
|
||
scp .env admin@<nas>:/share/Agent/scrum4me-agent-runner/.env
|
||
|
||
# image-tarball + bijgewerkte compose
|
||
scp scrum4me-agent-runner-amd64.tar.gz docker-compose.yml package.json README.md \
|
||
admin@<nas>:/share/Agent/scrum4me-agent-runner/
|
||
```
|
||
|
||
Zet de NAS-specifieke waarden in `.env`:
|
||
|
||
```bash
|
||
ssh admin@<nas> "
|
||
source /etc/profile
|
||
cd /share/Agent/scrum4me-agent-runner
|
||
sed -i \
|
||
-e 's|^NAS_BASE=.*|NAS_BASE=/share/Agent|' \
|
||
-e 's|^AGENT_BASE=.*|AGENT_BASE=/share/Agent|' \
|
||
-e 's|^AGENT_PLATFORM=.*|AGENT_PLATFORM=linux/amd64|' \
|
||
-e 's|^AGENT_UID=.*|AGENT_UID=1000|' \
|
||
-e 's|^AGENT_GID=.*|AGENT_GID=1000|' \
|
||
-e 's|^AGENT_HEALTH_PORT_HOST=.*|AGENT_HEALTH_PORT_HOST=18080|' \
|
||
.env
|
||
chmod 600 .env
|
||
"
|
||
```
|
||
|
||
> **`source /etc/profile` is verplicht** in non-interactieve ssh op QNAP. Zonder
|
||
> die regel staat `docker` niet op `$PATH` (Container Station's binary zit onder
|
||
> `/share/CACHEDEV*_DATA/.qpkg/container-station/...`) en faalt elk
|
||
> `docker`-commando met `command not found`.
|
||
|
||
### 5. Image laden + container recreaten
|
||
|
||
```bash
|
||
ssh admin@<nas> "
|
||
source /etc/profile
|
||
set -e
|
||
cd /share/Agent/scrum4me-agent-runner
|
||
|
||
# checksum-verificatie (optioneel maar aan te raden)
|
||
sha256sum scrum4me-agent-runner-amd64.tar.gz
|
||
|
||
# image laden — overschrijft bestaande :local tag
|
||
gunzip -c scrum4me-agent-runner-amd64.tar.gz | docker load
|
||
|
||
# container recreate; --no-build voorkomt onbedoelde NAS-side build
|
||
docker compose up -d --no-build --force-recreate agent
|
||
|
||
docker compose ps
|
||
curl -fsS http://localhost:18080/health | head -c 800
|
||
"
|
||
```
|
||
|
||
Verwacht in de health-output `cache_free_bytes` met een groot getal
|
||
(TB-orde) — dat is je signaal dat `/var/cache` op echte disk zit.
|
||
Een waarde rond `16 MB` (`16777216`) betekent dat je per ongeluk
|
||
nog steeds op tmpfs draait.
|
||
|
||
## Updaten (handmatig, bewust)
|
||
|
||
`SCRUM4ME_TOKEN` of `CLAUDE_CODE_OAUTH_TOKEN` rouleer je via een rebuild:
|
||
|
||
```bash
|
||
cd /share/Agent/scrum4me-agent-runner
|
||
git pull
|
||
vi .env # nieuwe waarden
|
||
docker compose build # nieuwe scrum4me-mcp-versie als dat veranderd is
|
||
docker compose up -d
|
||
```
|
||
|
||
Dezelfde flow voor schema-drift in scrum4me-mcp: pin een nieuwe
|
||
`MCP_GIT_REF` in `.env` of in `docker-compose.yml`, rebuild.
|
||
|
||
### Wijzigingen in `docker-compose.yml` (volumes, tmpfs, env_file, ports)
|
||
|
||
> **Let op:** `docker compose restart` herstart alleen het proces in de
|
||
> bestaande container met de **oude** config. Wijzigingen in volumes,
|
||
> tmpfs-mounts, env_file of ports worden daarmee **niet** doorgevoerd.
|
||
|
||
Gebruik altijd `--force-recreate` als je `docker-compose.yml` is veranderd:
|
||
|
||
```bash
|
||
docker compose up -d --force-recreate agent
|
||
```
|
||
|
||
Verifieer daarna dat `/var/cache` op de NAS-overlay staat en **niet** op tmpfs:
|
||
|
||
```bash
|
||
docker exec scrum4me-agent df -h /var/cache
|
||
# Verwacht: Filesystem op /dev/mapper/cachedev* of een NAS-share
|
||
# Fout: tmpfs 16M ... (dan is force-recreate niet uitgevoerd)
|
||
```
|
||
|
||
## Health-endpoint
|
||
|
||
`GET http://<nas>:18080/health` retourneert:
|
||
|
||
```json
|
||
{
|
||
"status": "running", // running | idle | unhealthy | token-expired
|
||
"lastBatchAt": "2026-05-01T12:34:56Z",
|
||
"lastBatchExit": 0,
|
||
"consecutiveFailures": 0,
|
||
"tokenStatus": { "anthropic": "ok", "scrum4me": "ok", "db": "ok" }
|
||
}
|
||
```
|
||
|
||
HTTP-status: `200` als running/idle, `503` bij token-expired of als de
|
||
laatste heartbeat ouder is dan 5 minuten.
|
||
|
||
## Filesystem-grenzen
|
||
|
||
De agent-user heeft geen SSH-keys en geen toegang tot andere shares dan
|
||
`/share/Agent/*`. Wel een `~/.git-credentials` met de `GH_TOKEN` voor
|
||
HTTPS-clone/push (zie volgende sectie) — die token is scoped tot de twee
|
||
configured repos en mag worden gerouleerd door rebuild + redeploy.
|
||
|
||
## Repo bootstrap (clone-on-start)
|
||
|
||
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.
|
||
2. Voor elke repo in `GH_PRECLONE_REPOS` (komma-gescheiden owner/name):
|
||
- Bestaat `~/Projects/<name>/.git` al? → `git fetch origin --prune`
|
||
- Anders → fresh `git clone`
|
||
|
||
Daarna vindt scrum4me-mcp's `resolveRepoRoot` (in `wait_for_job`) de
|
||
clone via z'n convention-fallback `~/Projects/<name>/.git`. Worktrees
|
||
voor jobs landen vervolgens onder `~/.scrum4me-agent-worktrees/<jobId>/`
|
||
zodat de hoofd-clone niet wordt aangeraakt.
|
||
|
||
Push gaat over dezelfde token: `git push -u origin feat/story-<id>`
|
||
slaagt zonder prompt. `gh pr create` (voor producten met `auto_pr=true`)
|
||
gebruikt dezelfde `GH_TOKEN` via de `gh` CLI's standaard env-detect.
|
||
|
||
## Veelvoorkomende issues
|
||
|
||
Verzameld uit echte deploy-sessies op een TS-664 met QTS 5.x. Eerst checken
|
||
voor je gaat tweaken.
|
||
|
||
### `/share/Agent` is `tmpfs 16M` — scp faalt op grote files
|
||
|
||
**Symptoom:**
|
||
```
|
||
scp: write remote ".../scrum4me-agent-runner-amd64.tar.gz": Failure
|
||
```
|
||
Eerst 100 % bij kleine files, dan blijvend "Failure" zodra een file de tmpfs
|
||
vol maakt.
|
||
|
||
**Oorzaak:** `/share/Agent` is geen geregistreerde QTS Shared Folder maar een
|
||
gewone directory in de root-tmpfs. Na elke reboot is alle inhoud weg en is
|
||
`/share/Agent` slechts een entry in de 16 MB `/share`-tmpfs.
|
||
|
||
**Fix:** maak `Agent` aan via Control Panel → Privilege → Shared Folders →
|
||
Create. Daarna verschijnt automatisch `lrwxrwxrwx /share/Agent → /share/CACHEDEV1_DATA/Agent`.
|
||
Als die symlink ontbreekt omdat er al een tmpfs-directory `/share/Agent` staat:
|
||
|
||
```bash
|
||
ssh admin@<nas> "
|
||
source /etc/profile
|
||
docker stop scrum4me-agent 2>/dev/null
|
||
docker rm scrum4me-agent 2>/dev/null
|
||
rm -rf /share/Agent
|
||
ln -s /share/CACHEDEV1_DATA/Agent /share/Agent
|
||
mkdir -p /share/Agent/{cache,logs,state,scrum4me-agent-runner}
|
||
ls -la /share/ | grep Agent # moet nu een symlink tonen
|
||
"
|
||
```
|
||
|
||
> **`rm -rf /share/Agent` is veilig** zolang `/share/Agent` nog tmpfs is —
|
||
> alle content stond in RAM en zou de volgende reboot toch verdwenen zijn.
|
||
> Maar verifieer eerst met `df -h /share/Agent` dat 't echt tmpfs is.
|
||
|
||
### `docker: command not found` in non-interactieve ssh
|
||
|
||
**Symptoom:**
|
||
```bash
|
||
ssh admin@<nas> 'docker ps'
|
||
# sh: line 1: docker: command not found
|
||
```
|
||
|
||
**Oorzaak:** QNAP's `admin`-user heeft `docker` op `$PATH` via
|
||
`/etc/profile.d/*.sh` van Container Station. Login-shells laden die scripts;
|
||
non-interactieve `ssh user@host 'cmd'` doet dat **niet**.
|
||
|
||
**Fix:** `source /etc/profile` aan het begin van je remote command:
|
||
|
||
```bash
|
||
ssh admin@<nas> "source /etc/profile && docker ps"
|
||
```
|
||
|
||
### `yaml: control characters are not allowed`
|
||
|
||
**Symptoom:**
|
||
```bash
|
||
docker compose down
|
||
# yaml: control characters are not allowed
|
||
```
|
||
|
||
**Oorzaak:** een eerdere `scp` van `docker-compose.yml` faalde halverwege en
|
||
liet een file met NUL-bytes / partial writes achter. De file-grootte klopt
|
||
maar de inhoud is corrupt.
|
||
|
||
**Fix:** scp opnieuw vanaf je werkstation. Voor het stoppen van een al-
|
||
draaiende container heb je de yml niet nodig:
|
||
|
||
```bash
|
||
docker stop scrum4me-agent && docker rm scrum4me-agent
|
||
```
|
||
|
||
Daarna `scp docker-compose.yml admin@<nas>:/share/Agent/scrum4me-agent-runner/`
|
||
en pas dan `docker compose up -d --no-build --force-recreate`.
|
||
|
||
### QTS-console-menu Python-traceback bij ssh-commando
|
||
|
||
**Symptoom:** lange Python-traceback over `consolemenu_q/prompt_utils.py:31`
|
||
en `Inappropriate ioctl for device`, gevolgd door je daadwerkelijke output.
|
||
|
||
**Oorzaak:** QTS' admin-shell start een Python-TUI (qts-console-menu) die
|
||
crasht zonder echte TTY. Niet-fataal — je commando wordt alsnog gerund.
|
||
|
||
**Fix:** negeer de traceback. Als je 'm écht weg wilt, gebruik
|
||
`ssh -tt admin@<nas> '...'` voor een geforceerde pseudo-TTY, maar pas op:
|
||
sommige scripts hangen daarop omdat ze interactie verwachten.
|
||
|
||
### `.env` is weg na `rm -rf /share/Agent`
|
||
|
||
**Symptoom:** na het opruimen van een tmpfs-`/share/Agent` weigert
|
||
`docker compose up`:
|
||
```
|
||
env file /share/Agent/scrum4me-agent-runner/.env not found
|
||
```
|
||
|
||
**Oorzaak:** `.env` zit in `.gitignore` en wordt nooit door `git pull` of `scp
|
||
.` automatisch teruggezet. Bij het wegnuken van een corrupte share verdwijnt-ie
|
||
mee.
|
||
|
||
**Fix:** scp 'm vanaf je werkstation, en patch path-velden op de NAS-zijde
|
||
(zie [Deploy — cross-build](#deploy--cross-build-vanaf-mac-apple-silicon--amd64-nas)
|
||
stap 4). Zorg dat je `.env` op je werkstation **alle** secrets bevat — vooral
|
||
`SCRUM4ME_TOKEN`, dat in een Mac-only `.env` makkelijk ontbreekt omdat
|
||
lokale tests soms zonder Scrum4Me-API draaien.
|
||
|
||
## Bekende grenzen
|
||
|
||
- **Eén actieve job tegelijk.** De wrapper-loop is sequentieel. Voor
|
||
parallellisme zou je meerdere containers met dezelfde `SCRUM4ME_TOKEN`
|
||
kunnen draaien — `wait_for_job` gebruikt `FOR UPDATE SKIP LOCKED` dus
|
||
dat is veilig op DB-niveau, maar dan moet je je `node_modules`-cache
|
||
per container scheiden.
|
||
- **OAuth-token: 1 jaar geldig.** Bij verloop schrijft de wrapper een
|
||
`TOKEN_EXPIRED`-marker en wordt de container `unhealthy`. Geen
|
||
auto-rotatie.
|
||
- **`npm install` per job** kost op een N5095 ~30–60 s per Next.js-clone,
|
||
óók met de pnpm-store. Voor zeer kleine fixes is dat de dominante
|
||
factor. Kan later vervangen worden door een persistente warm-`node_modules`
|
||
per repo als dat een knelpunt wordt.
|