Klokken 2 om natten starter et planlagt job på en VPS, der aldrig gik i dvale. En headless kørsel med claude -p kørsel arbejder sig gennem en køopgave i et klonet repo uden at spørge nogen om noget og afslutter derefter. Når du kigger om morgenen, venter der et commit, en rapport eller en log, der viser præcis hvor den stoppede og hvorfor. Ingen så på.
Det er en anden opsætning end at lade en terminal stå åben natten over og håbe på, at SSH-forbindelsen overlever. Et almindeligt fejlpunkt i en natlig agentkørsel er selve værten: en bærbar går i dvale, låget lukkes, netværket falder ud, eller en OS-opdatering genstarter maskinen midt i opgaven. Autentificeringsfejl, API-fejl og ventetid på tilladelser kan stadig dræbe jobbet, men en vært, der altid kører, fjerner den nemmeste fejlkilde.
Denne guide dækker den faktiske mekanik: de headless-flag, som hver større coding-agent-CLI leverer, de to måder at udløse en kørsel efter en plan og hvilken du bør vælge, hvad værten nedenunder kræver, og de værn, der forhindrer en uovervåget kørsel i at koste eller ødelægge mere, end du senere ville have lyst til at forklare.
Den korte version
- Alle større coding-agent-CLI'er leverer en dokumenteret ikke-interaktiv tilstand, der kører én prompt færdig og afslutter. Claude Code har
claude -p, Codex CLI harcodex exec, og Gemini CLI hargemini -p. Det er ikke en nødløsning, men en officiel funktion. - Claude Code har også sin egen planlægning: Routines, planlagte Desktop-opgaver og
/loop. For nogle læsere er det faktisk nok, og der er mindre at vedligeholde end en VPS. - Cron fungerer fint til et natligt job. En systemd-timer er det bedre standardvalg på en maskine, der kan genstarte, fordi
Persistent=truefanger en kørsel, som cron ville have sprunget over i stilhed. - Selve CLI'en er let, fordi inferensen sker på udbyderens API. Dimensioner VPS'en efter de kommandoer, den skal køre (tests, builds, containere, parallelle jobs), ikke efter modellen.
- Det er værnene (afgrænsede værktøjer, et loft over antal ture, forgrening på exit-koden), der gør det sikkert at lade en plan køre alene. Selve planen er ikke sikkerhedsmekanismen.
Hvad du får brug for
Hav disse fem ting klar, før du skriver en eneste crontab-linje eller unit-fil:
- En VPS, du kan tilgå via SSH, med en systemd-baseret Linux-distribution.
- Agent-CLI'en installeret på den VPS: Claude Code, Codex CLI eller Gemini CLI.
- En ikke-interaktiv legitimation til den CLI, du vælger. Bare-tilstanden i Claude Code læser ingen kontologin, så den kræver
ANTHROPIC_API_KEYi miljøet, eller enapiKeyHelperi indstillingerne. En almindelig kørsel i print-tilstand, Codex og Gemini kan også bruge de dokumenterede kontologin-oplysninger. - Et repo eller en opgavemappe, som agenten skal arbejde på.
- Shell-adgang med tilladelse til at redigere en crontab eller skrive en systemd unit-fil.
Kør en agent uden en tilknyttet session
Alle større coding-agent-CLI'er leverer en ikke-interaktiv tilstand bygget netop til dette. Claude Code tager -p, som også skrives --print. Codex CLI tager codex exec. Gemini CLI tager -p, som også skrives --prompt. De tager hver især en prompt, kører den færdig og afslutter. Ingen chatløkke, ingen terminal der skal holdes åben, intet at koble sig på igen.
Kan Claude Code køre uden en aktiv session? Ja. Med -p kører prompten i ikke-interaktiv tilstand: Claude Code udfører den færdig, udskriver resultatet og afslutter. Der er ingen chatløkke og intet at holde i live, og det kører på det samme Agent SDK som den interaktive CLI, ifølge Anthropics egen dokumentation for headless-tilstand.
| CLI | Ikke-interaktivt flag | Adfærd | Struktureret output |
|---|---|---|---|
| Claude Code | -p / --print | Kører prompten færdig, udskriver resultatet, afslutter | --output-format sat til text, json eller stream-json |
| Codex CLI | codex exec | Streamer fremdrift til stderr, skriver den endelige besked til stdout, afslutter | --json til en JSONL-hændelsesstrøm |
| Gemini CLI | -p / --prompt | Udfører prompten ikke-interaktivt, afslutter | --output-format json |
Her betyder Claude Codes egne flag mest, for det er dem, du reelt skriver scripts imod. To af dem lader en kørsel fortsætte uden at stoppe for en tilladelse, som ingen er vågen til at give: --allowedTools, som forhåndsgodkender bestemte værktøjer, og --permission-mode, som fastsætter udgangspunktet for hele kørslen. --max-turns begrænser, hvor mange agent-ture en kørsel må tage, før den afsluttes med en fejl.
--bare springer hooks, skills, plugins, MCP-servere og projektinstruktioner som CLAUDE.md, for en hurtigere og mere deterministisk scriptet kørsel. Det betyder også, at hver instruktion, jobbet afhænger af, skal stå i prompten eller kommandoen. Bare-tilstanden læser heller ikke din kontologin, så siger Anthropics dokumentation, at du skal sætte en API-nøgle i miljøet før du kører den. Claude Code afviser --bg blankt, når det kombineres med -p, og afviser --cloud på samme måde, når du giver den en opgavebeskrivelse. Den nævner konflikten og stopper i stedet for at gøre noget tvetydigt.
Et gennemarbejdet kald, tilpas prompten og værktøjslisten til din opgave:
claude --bare -p "Review open PRs in this repo and summarize any blockers in NOTES.md" \
--allowedTools "Bash(gh pr list *),Bash(gh pr view *),Bash(gh pr diff *),Read,Edit" \
--permission-mode dontAsk \
--max-turns 8 \
--max-budget-usd 5.00 \
--output-format json
Tilpas budgettet og kommandomønstrene til opgaven; dette eksempel forudsætter også, at GitHub CLI-godkendelse allerede er sat op for den konto, der kører det.
Hvis du sætter Claude Code op på en frisk VPS og vil have gennemgangen af, hvordan du godkender den på en maskine uden browser, er det dækket separat i sådan godkender du Claude Code på en headless server; den korte version ovenfor er nok til at få en planlagt kørsel til at virke.
Tilstanden exec i Codex CLI, beskrevet i OpenAIs dokumentation for ikke-interaktiv tilstand, tager --sandbox til at vælge en politik. read-only er standardværdien, workspace-write lader agenten skrive inde i sit workspace, og --json gør stdout til en maskinlæsbar hændelsesstrøm i stedet for ren tekst. Undgå danger-full-access til et uovervåget job, medmindre processen er isoleret, og den risiko er bevidst.
Gemini CLI's headless-tilstand, dokumenteret i projektets egen headless-dokumentation, aktiveres automatisk i et miljø uden TTY, eller eksplicit med -p. Den afsluttes med en specifik exit-kode forskellig fra nul for en generel fejl, en inputfejl eller en nået turgrænse i stedet for én generisk fejlkode.
Hvor planen bør bo
Før alt dette opsætningsarbejde: agentleverandøren planlægger måske allerede dette for dig. Claude Code tilbyder tre indbyggede muligheder, og en af dem passer måske faktisk bedre end en selvadministreret VPS.
| Cloud (Routines) | Planlagt Desktop-opgave | /loop | |
|---|---|---|---|
| Kører på | Anthropics cloud | Din maskine | Din maskine |
| Maskinen skal være tændt | Ikke påkrævet | Påkrævet | Påkrævet |
| Kræver en åben session | Ikke påkrævet | Ikke påkrævet | Påkrævet |
| Minimumsinterval | 1 time | 1 minut | 1 minut |
| Adgang til lokale filer | Ingen, den kører fra en frisk klon | Fuld adgang | Fuld adgang |
Anthropics egen dokumentation om planlagte opgaver fremstiller dette som et reelt trevejsvalg, ikke et hierarki med VPS'en øverst. Hvis din opgave ikke kræver lokal tilstand, kan tåle en nedre grænse på en time, og du kun bruger Claude Code, kræver Routines mindre vedligeholdelse end det følgende: Anthropic kører det i skyen fra en frisk klon, mens din maskine er slukket.
/loop er værd at kende, men passer ikke til dette formål, for den kræver en åben, inaktiv session, hvilket er præcis den begrænsning, du prøver at fjerne. Den samme dokumentation peger også på GitHub Actions som en fjerde mulighed, til teams hvis trigger i forvejen lever i CI frem for i en plan bundet til én bestemt maskine.
Den selvadministrerede VPS gør sig fortjent til sin plads, når jobbet kræver fuld adgang til det lokale filsystem og værktøjerne, når du vil have den samme mekanisme til at fungere ens på tværs af Claude Code, Codex CLI og Gemini CLI, eller når det interval, Routines tillader, er for groft. En almindelig serverless-funktion passer typisk dårligt her, fordi den skal gendanne legitimationsoplysninger, klone repoet og blive færdig inden for platformens køretidsgrænser. En kortlivet CI-runner som GitHub Actions er stadig en gyldig tredje vej, når et frisk checkout pr. kørsel er acceptabelt. Hvis du allerede har hardware, der kører døgnet rundt og står ubrugt, kan en homelab-maskine også gøre det; byttet er dit hjemmenetværks pålidelighed og fjernadgang i stedet for en udbyders.
Cron eller en systemd-timer?
Begge værktøjer kan udløse den samme kommando efter den samme plan, men de adskiller sig i, hvad der sker, når maskinen genstarter, og i hvor meget opsætning hver af dem koster:
| cron | systemd-timer | |
|---|---|---|
| Opsætningsbyrde | Én crontab-linje | En .timer- og en .service-fil |
| Indhentning af mistede kørsler | Ingen, en oversprunget kørsel er bare væk | Persistent=true kører den, så snart systemet er oppe igen |
| Logning | Manuelt, du omdirigerer selv output | Automatisk, opsamlet af journald |
| Rækkefølge af afhængigheder | Intet | Fuld systemd-rækkefølge med After= og Requires= |
Almindelig cron er fin til et natligt job på en maskine, der sjældent genstarter. Hagen er miljøet: cron starter med en minimal PATHPATH, går ikke ind i dit repo for dig, og starter gladeligt en kopi nummer to, mens den første stadig kører. Læg repo-stien, den snævre agentkommando og indlæsningen af legitimationsoplysninger i et beskyttet wrapper-script, og brug derefter flock til at forhindre overlappende kørsler.
# /usr/local/bin/agent-nightly
#!/usr/bin/env bash
set -euo pipefail
export PATH=/usr/local/bin:/usr/bin:/bin
export ANTHROPIC_API_KEY="$(
cat "$HOME/.config/agent-nightly/anthropic_api_key"
)"
cd /srv/myrepo
exec /usr/local/bin/claude --bare -p \
"Run the nightly dependency audit and write the findings to NOTES.md" \
--allowedTools "Bash(npm audit *),Read,Edit" \
--permission-mode dontAsk \
--max-turns 8 \
--max-budget-usd 5.00 \
--output-format json
# crontab -e
0 2 * * * /usr/bin/flock -n "$HOME/.local/state/agent-runs/nightly.lock" /usr/local/bin/agent-nightly >> "$HOME/.local/state/agent-runs/nightly.log" 2>&1
Opret mapperne til legitimationsoplysninger og logs én gang, og gør derefter wrapper-scriptet eksekverbart:
install -d -m 700 \
"$HOME/.config/agent-nightly" \
"$HOME/.local/state/agent-runs"
touch "$HOME/.config/agent-nightly/anthropic_api_key"
chmod 600 "$HOME/.config/agent-nightly/anthropic_api_key"
"${EDITOR:-nano}" \
"$HOME/.config/agent-nightly/anthropic_api_key"
sudo chmod 755 /usr/local/bin/agent-nightly
Indsæt kun API-nøglen i legitimationsfilen. Læg den ikke direkte i crontab'en.
En systemd-timer kræver mere opsætning og giver dig to ting, cron ikke har: logning via journald uden hjemmestrikket omdirigering, og Persistent=true. Eksemplet nedenfor forudsætter, at en dedikeret konto agent-runner ejer /srv/myrepo. Gem API-nøglen i en legitimationsfil, som kun root kan læse, i stedet for at indlejre den i unit-filen.
Pr. systemd.timer-manualen betyder det at sætte Persistent=true betyder, at "service-unitten udløses med det samme, hvis den ville være blevet udløst mindst én gang i den periode, hvor timeren var inaktiv." En kørsel, der ville være gået i gang, mens din VPS genstartede efter en kerneopdatering, går altså i gang, så snart den er tilbage, i stedet for at forsvinde i stilhed indtil næste planlagte tidspunkt.
Opret den legitimationsfil, som kun root kan læse, og som tjenesten bruger:
sudo install -d -m 700 /etc/agent-nightly
sudo touch /etc/agent-nightly/anthropic_api_key
sudo chmod 600 /etc/agent-nightly/anthropic_api_key
sudoedit /etc/agent-nightly/anthropic_api_key
Indsæt kun API-nøglen i filen.
# /etc/systemd/system/agent-nightly.service
[Unit]
Description=Nightly scoped agent run
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=agent-runner
Group=agent-runner
WorkingDirectory=/srv/myrepo
Environment=HOME=/home/agent-runner
Environment=PATH=/usr/local/bin:/usr/bin:/bin
LoadCredential=anthropic_api_key:/etc/agent-nightly/anthropic_api_key
ExecStart=/bin/sh -c 'export ANTHROPIC_API_KEY="$(cat "$CREDENTIALS_DIRECTORY/anthropic_api_key")"; exec /usr/local/bin/claude --bare -p "Run the nightly dependency audit and write the findings to NOTES.md" --allowedTools "Bash(npm audit *),Read,Edit" --permission-mode dontAsk --max-turns 8 --max-budget-usd 5.00 --output-format json'
StandardOutput=journal
StandardError=journal
UMask=0077
# /etc/systemd/system/agent-nightly.timer
[Unit]
Description=Run agent-nightly.service at 2am daily, catching up missed runs
[Timer]
# Uses the VPS's configured local timezone
OnCalendar=*-*-* 02:00:00
Persistent=true
Unit=agent-nightly.service
[Install]
WantedBy=timers.target
Genindlæs systemd, aktivér timeren, og kør tjenesten én gang med det samme, så problemer med legitimation, tilladelser og stier dukker op nu i stedet for klokken 2 om natten:
sudo systemctl daemon-reload
sudo systemctl enable --now agent-nightly.timer
sudo systemctl start agent-nightly.service
systemctl list-timers agent-nightly.timer
sudo journalctl \
-u agent-nightly.service \
-n 100 \
--no-pager
Persistent=true er den afgørende forskel: timeren husker en mistet kalenderkørsel i stedet for at droppe den i stilhed.
Hvad VPS'en faktisk kræver
Her er den del, der overrasker folk, som dimensionerer det for første gang: selve CLI'en er let, fordi inferensen sker på udbyderens API. Men agenten kan stadig starte builds, tests, pakkehåndtering, sprogservere og containere lokalt, så repoets arbejdsbyrde sætter det reelle gulv.
Tag 1 til 2 vCPU og 2 til 4 GB RAM med NVMe-lagring som udgangspunkt for ét let planlagt job. Store repoer, compilere, Docker-builds, testsuiter eller samtidige kørsler kan kræve langt mere. Det, der tvinger dig til at skalere op, er den tungeste lokale kommando, agenten kører, ikke modellen bag API'en. Hvis du allerede kører Docker-arbejdsbelastninger på denne VPS og vil have et mere komplet billede af budgettet, dimensionering og sikring af en build-maskine gennemgår den samme afvejning for en anden uovervåget arbejdsbelastning.
Én ting mere, det er værd at planlægge efter: en uovervåget kørsel producerer logs hver nat, uanset om noget gik galt. Tilføj logrotate til, hvis cron skriver til en fil, og tjek journalds opbevaringsgrænser i stedet for at antage, at standardværdierne passer til VPS'ens disk.
Hele tilgangen afhænger af en vært, der er vågen klokken 2 om natten og bliver ved med at være det, uanset hvad din bærbare laver. Det er præcis den opgave, en Linux VPS med root-adgang er bygget til. Intet sætter den i dvale, og du deler den ikke med andres cron-jobs.
Byg på en Linux VPS med root-adgang, NVMe og AMD EPYC-kraft.
Se Linux-planerSådan undgår du, at en uovervåget kørsel går galt
Den største forskel mellem en planlagt kørsel, der virker, og en der ikke gør, er, om opgaven er snæver nok til at blive færdig uden at et menneske svarer på et spørgsmål undervejs. Ambitiøse prompts går i stå og venter på en beslutning, ingen er der til at træffe; snævre, selvstændige opgaver kører færdigt og afslutter pænt.
De to tilladelsesflag findes, så en kørsel ikke går i stå på en forespørgsel klokken 2 om natten, men bar Bash adgang er ikke et snævert værn: den kan næsten alt, hvad servicekontoen kan. Foretræk kommandospecifikke regler som Bash(git status *), kombinér dem med --permission-mode dontAsk, og kør tjenesten under en dedikeret ikke-root-konto. Antal ture og forbrug har hver deres loft: --max-turns begrænser, hvor længe agenten kan vandre rundt, og --max-budget-usd sætter loft over, hvad en enkelt kørsel må bruge på API-kald.
Godt råd: kør med --output-format json og log feltet total_cost_usd fra hvert kald. Det er den reneste krog til at følge, hvad en planlagt kørsel faktisk koster pr. nat, og til at give besked, når én kørsel koster mærkbart mere end de andre. De fem minutter, det tager at sætte op, er det værd, for det er din regning, den følger, ikke en abstraktion.
Omkostningsoverskridelser ved uovervågede kørsler er ikke hypotetiske. I et indlæg på Hacker Newsrapporterede en bruger en AWS Bedrock-bruttoregning på 37.901,73 dollars fra et dagligt coding-agent-workflow, hvor prompt-caching kun virkede delvist og efterlod omkring 6,47 milliarder input-tokens uden cache. Det skete i en anden stak, ikke i Claude Codes headless-tilstand, men det viser, hvorfor omkostningslogning og et hårdt budget pr. kørsel hører hjemme i planen.
Godt råd: Claude Code afsluttes med kode 0 ved succes og en kode forskellig fra nul ved fejl. Et wrapper-script, der tjekker exit-status, kan sende dig en notifikation ved fejl, så en dårlig nat dukker op næste morgen i stedet for tre dage senere, når du tilfældigvis kigger.
Kør som minimum hvert job på en dedikeret gren eller et engangs-worktree, og kræv menneskelig gennemgang før merge. Afgrænsede legitimationsoplysninger, filsystemisolering og kontrol af skadesradius på serverniveau er et større emne, der fortjener sin egen behandling frem for et afsnit klistret bag på en planlægningsguide.
Det er værnene, der gør det sikkert at lade planen være i fred: selve planen er ikke sikkerhedsmekanismen.
Hvornår cron ikke længere slår til
Én prompt på en timer kræver ikke mere end det, der allerede er dækket her. Tre kædede trin med en betingelse, et retry og en Slack-notifikation kræver noget andet.
Tre muligheder er værd at kende, hver især et trin op af forskellige grunde:
- Dagu er det letteste trin op: selvstændige, YAML-definerede jobs med DAG-afhængigheder, retries og en web-UI til at se, hvad der er kørt.
- n8n passer bedst, når agentkørslen er én knude blandt flere integrationer og notifikationer frem for hele workflowet.
- Kestra er den tungeste af de tre, bygget til at orkestrere data- og infrastrukturpipelines, og den rigtige løsning, når planlægningen af agenten er en del af en større pipeline frem for selve pointen.
For læseren, der kører én prompt om natten, er alle tre overkill, og det er værd at sige lige ud frem for at snakke dig til en tungere opsætning, end du har brug for. Hvis en kæde af trin med tiden retfærdiggør en af dem, Dagu, n8n, og Kestra kan alle udrulles med ét klik, hvilket er en reel bekvemmelighed netop i det øjeblik, hvor du overvejer, om opsætningsomkostningen er det værd.
Multi-agent-orkestreringsframeworks som LangChain eller CrewAI er et helt andet emne: at bygge agentsystemer frem for at planlægge en CLI, der allerede findes.
Ofte stillede spørgsmål
Kan Claude Code køre uden en aktiv session?
Ja. Med -p kører prompten i ikke-interaktiv tilstand: Claude Code udfører den færdig, udskriver resultatet og afslutter, uden chatløkke og uden en session, der skal holdes åben.
Har jeg brug for en VPS, når Claude Code allerede har Routines?
Ikke altid. Routines kører i Anthropics cloud, mens din maskine er slukket, og starter fra en frisk klon, men den kan ikke tilgå filer, der kun findes på din maskine, og har et minimumsinterval på én time. En selvadministreret VPS gør sig fortjent til sin plads, når opgaven kræver lokale filer, vilkårlige intervaller eller en mekanisme, der fungerer ens på tværs af flere leverandørers CLI'er.
Skal jeg bruge cron eller en systemd-timer til en planlagt agent?
En systemd-timer, hvis VPS'en nogensinde genstarter til vedligeholdelse. Persistent=true kører et job, der ville være udløst under nedetiden, så snart systemet er tilbage, og det har cron ingen pendant til. Cron er fin til et natligt job på en maskine, der bliver ved med at køre.
Hvor meget RAM kræver en planlagt AI-agent på en VPS?
Start omkring 1 til 2 vCPU og 2 til 4 GB RAM til ét let planlagt job, og dimensioner derefter efter den tungeste lokale kommando, agenten kører. Builds, tests, Docker, store repositories og samtidige kørsler betyder langt mere end den fjerne modelinferens.
Ændrer det faktureringen at køre en agent efter en plan?
Planlægning skaber ikke en separat faktureringsmodel. Claude Code -p kan bruge abonnementsoplysninger eller en API-nøgle, men --bare ignorerer abonnementsloginet, så den kræver ANTHROPIC_API_KEY i miljøet, eller en apiKeyHelper i indstillingerne. Codex og Gemini følger den godkendelsesmetode, du har konfigureret til deres CLI. Da priser og brugsvilkår ændrer sig hurtigt, bør du tjekke udbyderens aktuelle priser og dine egne forbrugsdata, når du sætter dette op. Til Claude Code-kørsler via API'en kan du desuden logge feltet total_cost_usd fra JSON-outputtet.
Diskussion
Kommentarer
Log ind for at deltage i diskussionen.