Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions modules/agent-box.nix

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions modules/agent-box.nix.in
Original file line number Diff line number Diff line change
Expand Up @@ -1616,10 +1616,26 @@ ${lib.optionalString (agentsMdPointer != null) ''
# nested inspection bash.
postmortem=" || exec ${pkgs.bashInteractive}/bin/bash"
[ "$agent" = shell ] && postmortem=""
# A delete (settings page / agent-box-session rm: delist THEN kill) can
# land while this function is preparing the spawn — their kill hits the
# OLD session and this spawn would resurrect it as a live, delisted
# session the reconcile loop never kills (it tolerates unmanaged
# sessions on purpose): a permanent leak. Re-check the CURRENT file at
# the last moment, and verify again AFTER the spawn — a delist landing
# before the post-check is honored here by killing what we just
# started; one landing after it sees the session live and kills it
# itself. (Seen as a CI flake in the sessions VM test, run 30740226645.)
$JQ -e --arg s "$sname" '.sessions | has($s)' "$SESSIONS_FILE" >/dev/null 2>&1 || return 0
if $TMUX new-session -d -s "$sname" -c "$wd" ${webhookSessionEnvArgs name} \
"${envExecWrapper name} $cmd$postmortem"; then
if ! $JQ -e --arg s "$sname" '.sessions | has($s)' "$SESSIONS_FILE" >/dev/null 2>&1; then
$TMUX kill-session -t "=$sname" 2>/dev/null || true
return 0
fi
# First spawn only: persist the id + hasRun and consume the kickoff
# prompt, so the next respawn resumes instead of redoing the task.
# (Ordered after the delist post-check: mark_started's jq assignment
# would otherwise re-create a just-deleted session as a stub entry.)
[ "$resuming" = true ] || mark_started "$sname" "$bid"
fi
}
Expand Down
15 changes: 15 additions & 0 deletions tests/webhook.nix
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,13 @@
# A signed delivery on the watched repo → a fresh hook-* session appears in
# sessions.json, primed with the framed event text plus the trusted
# preamble, and the supervisor starts it as a real tmux session.
#
# The supervisor is STOPPED for the delivery: it consumes a kickoff prompt
# within ~2s of spawning (mark_started nulls initialPrompt), so asserting
# the prompt via sessions.json is a race otherwise (lost on master run
# 30740226645). With it stopped, the wrapper's write is the only actor;
# restarting it afterwards proves the spawn + consumption half.
machine.succeed("systemctl stop agent-box-agent.service")
client.succeed(
f"{post} -H 'x-hub-signature-256: sha256={sig}' "
f"https://box.test/agent/webhook/github | grep -x 200"
Expand All @@ -352,11 +359,19 @@
"jq -e '.sessions | keys[] | select(startswith(\"hook-defangdevs-agent-box-\"))'"
" /home/agent/.config/agent-box/sessions.json"
)
# Supervisor back up: it starts the hook session and consumes the prompt.
machine.succeed("systemctl start agent-box-agent.service")
machine.wait_until_succeeds(
"sudo -u agent env TMUX_TMPDIR=/run/agent-box-agent tmux -L agent-box"
" list-sessions -F '#S' | grep -q '^hook-'",
timeout=60,
)
machine.wait_until_succeeds(
"jq -e '.sessions | to_entries[] | select(.key | startswith(\"hook-\"))"
" | .value | (.hasRun == true and .initialPrompt == null)'"
" /home/agent/.config/agent-box/sessions.json",
timeout=60,
)

# The event that spawned the watch session was NOT also a session delivery
# for the peer (its filter has someone else's repo) — dispatch and session
Expand Down