redeploy-fleetd.sh knows which supervisor it is talking to (#492) but not which one it is watching: the log source and the pid count are both still macOS-shaped, and on systemd they fail in opposite directions #593
Open
opened 2026-09-19 09:56:43 +02:00 by ltms
·
1 comment
No Branch/Tag Specified
main
worker/fleetd-612-unita-87807e-1
worker/612-b3-mcpwirings-da2b58-3
worker/612-b2-cb185-176d3a-2
worker/612-b1-completion-457459-1
worker/612-agaps-73a926-2
worker/608-sleeps-3a64ff-3
worker/621-b4520b-1
worker/618-b83894-2
worker/fleetd-615-e05481-5
worker/lead-autocompact-5f1ab2-3
worker/fleetd-613-f85deb-3
worker/fleetd-608-flaky-nudge-test-d0c2d1-3
worker/lead-context-gauge-ad404f-1
worker/gauge-wiring-9158c1-4
worker/redeploy-slowstart-ead0e5-5
worker/charter-bytes-13668c-6
worker/rollover-outcome-291483-2
worker/589-f64303-2
worker/593-1a8025-5
worker/589-fcd2aa-1
worker/568-9fdaa2-3
worker/571-attempted-outcome-5739f7-2
worker/581-completionresolver-cas-sites-0542b7-6
worker/562-loop-health-wiring-test-99611c-5
worker/562-surface-loop-health-7df5cc-4
worker/575-waiter-cleanup-sites-62ad80-1
worker/572-answer-lock-release-46a9ae-5
worker/567-probe-channel-leak-a38fc5-6
worker/551-record-before-send-7cbf56-1
worker/561-listener-fanout-survives-a-throw-61d538-2
worker/555-redeploy-main-flow-seam-65c2f5-2
worker/556-injector-owns-registration-e027a5-1
worker/552-post-restart-mktemp-abort-bc2672-4
worker/553-onstatus-completion-leak-0da881-2
worker/550-shasum-linux-196132-1
worker/538-loop-dies-on-error-4a5eeb-6
worker/426-health-coverage-ef1fd4-4
worker/504-failed-reported-clean-3cfd66-3
worker/537-capturedlog-close-e4c437-2
worker/459-broken-link-targets-cadc17-5
worker/535-appender-leak-fe74c1-1
worker/512-part2-shutdown-detection-434701-9
worker/529-logger-level-sweep-2a5533-8
worker/528-drain-gate-call-site-5de83d-7
charter/forge-mcp-vs-token
worker/521-swap-guard-unpinned-28e931-5
worker/519-probe-test-harness-d25ab8-4
worker/525-logger-level-leak-1b4eb0-6
worker/518-fleetmcp-resolver-wiring-8ef96c-1
worker/512-drain-complete-line-7edd71-3
worker/517-abort-branch-and-jar-id-41b641-2
worker/500-9e52c9-3
worker/509-4912f4-2
worker/511-9a4b23-1
worker/493-479f45-2
worker/505-03f8b2-1
worker/492-followup-detect-unclear
worker/501-a31fa0-7
worker/498-451d1c-5
worker/494-1015ce-2
worker/492-209647-1
worker/489-001902-2
worker/480-relative-handover-path-906323-1
worker/480-b-handover-skill-45bf1f-5
worker/474-followup-source-pin-f54a55-17
worker/474-charter-check-on-reload-f54a55-17
worker/466-quarantine-repeatcount-report
worker/393-opencode-skill-seeding-71854b-13
worker/469-canonical-tool-names-2a472a-16
worker/466-quarantine-escalation-5ae9c1-15
worker/446-hot-exhausted-pattern-0af580-6
worker/464-charter-tool-name-guard-a85635-12
worker/463-listfleet-default-fails-open-f1c76c-11
worker/458-invariant-5-by-purpose-862f9a-10
worker/439-coordinator-row-gate-bc032a-8
worker/449-herdr-protocol-576015-4
worker/450-abstract-spawn-599e1c-5
worker/437-ack-refuses-177d91-1
worker/444-placement-window-feb56a-2
worker/440-helddurable-derived-d462d7-13
worker/425-rework-placement-resolve-c58ba1-9
worker/421-lead-peek-held-msgs-cdbad2-10
worker/435-fixed-policy-cap-fe11de-12
worker/422-gate-state-observability-9e79d6-11
worker/431-memberregistry-live-readers-cdbad2-10
worker/424-architect-slot-hot-038b41-7
worker/422-model-gate-spawn-c29f48-6
worker/425-default-profile-live-f55534-8
worker/415-coverage-wording-2cbf9c-5
worker/416-3ad1da-1
worker/418-588283-3
worker/deterministic-stamp-race-409-3cb7b6-10
worker/armed-reads-live-config-404-ed931f-9
worker/reply-peer-refusal-391-5a34bd-7
worker/models-allowlist-aa9e9b-3
worker/ttl-stamp-race-399-f1122f-8
worker/scrub-receipt-400-316b3e-5
worker/exhaustion-detection-395-105105-6
worker/scrub-abort-394-316b3e-5
fix/scrub-uid-abort
worker/task-scrub-517574-2
worker/t386-clock-bd5b78-4
worker/t384-scrub-813790-5
worker/t381-cc-748314-2
worker/t373-336973-2
worker/t365-3920c5-3
worker/t358-6e989b-1
worker/t355-8b321c-1
worker/fleetd-369-hermetic-git-tests-e8b19a-3
worker/fleetd-368-stale-lead-binding-f5682e-2
worker/fleetd-360-deploy-units-0d3793-1
worker/359-dead-lead-tabs-f1253b-4
worker/362-worktree-skills-c03e51-3
worker/361-coord-visibility-655144-1
362-plugin-visibility-and-drift
worker/errscan-bed2ca-2
worker/amqp-log-identity-bed2ca-2
worker/withdefaults-guard-561704
worker/sleepguard-82076d-1
worker/fd334-9ee1b6-5
worker/fd348-f1ab27-4
worker/fd335-a71c35-1
worker/fd342-174a17-2
worker/fd345-490d0f-3
worker/fleetd-337-5ec7d4-21
worker/fleetd-341-af5a6b-24
worker/fleetd-339-5ca0a2-23
worker/fleetd-338-83a4a1-22
worker/fleetd-333-281f46-18
worker/fleetd-329-11bdbb-16
worker/fleetd-330-2770fb-17
worker/fix-326-50506e-15
worker/fix-324-3e9bbf-14
worker/fix-323-b8287d-13
worker/fix-316b-bd0860-11
worker/fix-318-76ca36-9
worker/fix-317-486aec-8
worker/fix-315-ce47c5-6
worker/fix-307-275890-6
worker/fix-308-b4f664-7
worker/fix-309-ec3939-8
worker/fix-310-7a3974-9
worker/fix-302-52ad0e-9
worker/fix-298-ce1acb-8
worker/fix-297-66bd11-7
worker/fix-296-104622-6
worker/fix-293-bare-closetab-eb22b5-3
worker/fix-280-gone-ask-lapse-bca98e-2
worker/fix-290-reapidle-guard-coverage-9b0dd1-1
worker/fix-285-trust-seed-8f3565-10
worker/fix-284-backend-error-seat-85912c-11
worker/fix-282-chained-ask-e6d0bb-8
worker/fix-283-teardown-leaks-f40dfa-9
worker/fix-281-pin-handler-actions-4921ac-7
worker/audit-rendezvous-lifecycle-d072ae-2
worker/audit-health-placement-1a2476-6
worker/audit-teardown-exits-e207a5-3
worker/audit-launcher-asymmetry-27e370-4
worker/audit-rest-authz-6ca53c-5
worker/investigate-275-abandon-asking-fdef52-8
worker/fix-274-worktree-leak-b0095d-7
worker/fix-273-exhausted-pattern-9665b5-6
worker/fleetd-267-model-check-bd8068-1
worker/fleetd-131-archunit-18b834-7
worker/fleetd-266-sshagent-rename-a014ff-6
worker/fleetd-184-uid-claim-8e1f31-4
worker/fleetd-184-warn-b381ee-10
worker/fleetd-184-docs-be1d12-9
worker/fleetd-257-9bf010-7
worker/fleetd-103-23a113-6
worker/fleetd-247-342356-5
worker/fleetd-116-04dea8-4
worker/fleetd-252-a830e0-3
worker/fleetd-111-7e8673-9
worker/fleetd-155c-f8ef4b-8
worker/fleetd-176-b928ca-3
worker/fleetd-249-7a7878-2
worker/cb248-composition-root-b-9acdf7-15
worker/cb148-envrc-default-fa6c82-12
worker/cb201-unit5-wiring-6c12e6-8
worker/cb241-fallback-echo-1175e9-11
worker/cb149-trust-dialog-2392a5-9
worker/cb134-148-overlay-visible-c9b986-10
worker/cb234-session-id-keyed-04e1fc-1
worker/cb201-unit3-nudge-abdf5c-6
worker/cb201-unit2-policy-c1102c-5
worker/cb201-unit4-outcome-a13bfa-7
worker/cb201-unit1-classifier-91b9b1-4
worker/cb201-227-refine-831980-3
worker/cb175-model-readback-0f085f-1
worker/cb222-charter-tmpdir-17f013-1
worker/cb226-architect-slot-race-cd3aa8-3
worker/cb224-worktree-root-group-024523-2
worker/cb-123-role-demotion-c600f7-2
worker/cb-219-opencode-roots-1f677e-1
worker/cb214-claude-session-id-b9eab4-4
worker/cb213-zdotdir-wrong-process-dd6de4-3
worker/cb211-exhaustion-classification-9546e0-2
worker/cb137-ambiguous-task-4df3d8-4
worker/cb209-agentsessionid-4dfdb6-2
worker/cb185-hostenvnames-2692b5-3
worker/cb206-opencode-sqlite-128718-2
worker/cb185-worktree-group-fc0c99-1
worker/cb-137-ask-ticket-e7760c-2
worker/cb-172-broker-uri-d36ae4-4
worker/cb-175-model-readback-76ead6-3
worker/cb-161-pane-ancestry-293510-1
worker/cb-164-rebase-885863-8
worker/cb-164-empty-scrape-false-success-1a80af-3
fix/cb-197-ticket-ttl-from-completion
worker/cb-189-remote-url-coverage-4692f3-1
worker/cb-185-blockers-027756-4
worker/cb-192-gap-log-11b631-2
worker/cb-633-fix-5f4396-3
worker/cb185-router-d6436d-3
worker/cb185-router-routing-gaps-9e9d33-3
worker/cb185-paneids-992586-2
worker/cb-633-allow-list-union-ed374b-1
worker/cb-157-credential-in-remote-url-496e44-2
worker/cb-641-health-herdr-evidence-8f1f54-6
worker/cb-640-health-msg-evidence-99c9cd-1
worker/cb-642-fleets-status-skill-bbbc40-5
cb-634-ide-mcp
worker/lead-comms-wiring-c014b9-7
worker/lead-mailbox-c19577-6
worker/autocompact-window-82bc2f-5
worker/cb-634-probe-18056f-4
worker/cb635-broker-urienv
worker/cb-632-config-retry-8e0efa-7
lead/cb-622e-claude-md
lead/cb-622-followup
worker/cb-622a-165dff-1
lead/cb-622d-opencode-mount
worker/cb-622b-717c67-2
worker/cb-622c-ab7759-3
worker/cb-617b2-20ca4b-3
worker/cb-617a-5c2f4a-1
worker/cb596-4e49ef-3
worker/cb586-10500c-1
worker/cb-606-b9343a-25
worker/cb604-1445f8-24
worker/cb582-477374-21
worker/cb584-8c2281-22
worker/cb600-e6b9a9-20
worker/cb602-ce257f-19
worker/cb601-b42837-18
worker/cb598-6c7ba7-17
worker/cb599-740fe4-16
worker/cb597-282224-15
worker/cb590fix-185e9a-10
worker/cb528-recovery-race
worker/cb594-96bead-8
worker/cb590-916766-2
worker/cb527-997d99-3
worker/cb592-env-leak-3cbf9c-1
worker/cb588-async-ticket-nudge-3218f7-5
worker/cb578b-9dcb13-6
worker/cb581-d24826-5
worker/m2-u5-ef8c42-15
worker/cb578a-516499-2
worker/cb576-01a04b-17
worker/cb579-lead-tab-acba06-20
worker/cb580-terminal-health-ed6058-21
worker/cb577-f36fdc-18
worker/cb573b-3db06f-16
worker/cb568c-f36fdc-18
worker/cb568-drop-cause-c3ac1c
worker/cb575-cancelled-notification-c3ac1c
worker/m4-sol-a2cbec-3
worker/cb574-async-ask-c3ac1c
worker/cb573-health-model-8ca857-14
worker/cb572-unknown-target-7f2e35-13
worker/u4-700706-9
worker/u3-b9fcb6-6
worker/u2-ef5b68-4
worker/u1-469dce-1-clean
worker/u1-469dce-1
worker/cb-564-health-events-70cf7e-2
worker/cb-565-recycle-drops-role-98e58f-3
worker/cb-563-missing-reply-df2866-1
worker/cb-562-readiness-gate-silent-6c23c9-3
worker/cb-560-architect-presence-da8155-1
worker/cb-561-architect-silent-off-a71cab-2
worker/cb-548-bind-architect-slot-fe1b8c-1
worker/parity-overlay-settings-5fb711-1
secrets-central-store
cb-559-hot-key-correction
cb-557-fleet-role-pools
worker/cb-553-maxload-explicit-spawn-305ee3-6
worker/cb-551-idle-lead-heartbeat-f1633c-1
worker/cb-544-drain-preserves-worktree-925fad-3
worker/cb-552-docs-sync-1cb9cf-4
worker/cb-548-rendezvous-guard-rebased
worker/cb-548-rendezvous-guard-116b53-10
worker/cb-548-authz-v2-586df6-8
worker/cb-548-authz-264363-5
salvage/cb-528b-codex-home
salvage/cb-528a-codex-launcher
CB-518-primary-flow
feature/peer-launcher-spi
cb-103-injector
v1.1.0
v1.0.0
Labels
Clear labels
blocked
needs-live-proof
ready-to-delegate
silent-default
Cannot start until something else lands. The body says what.
Merged and green, but never shown working on the running daemon. Not the same as done.
Scope, files and acceptance criteria are written. A worker can be briefed from the body alone.
A feature that compiles, passes tests, and ships turned off. Nine recurrences and counting.
No Label
Milestone
No items
No Milestone
Projects
Clear projects
No project
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: fleet/fleetd#593
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The shape
#492made the script's control plane supervisor-aware: it correctly picks launchd,systemd, or unsupervised, and the fleet01 run confirmed it used
systemctl --user stopandsystemctl --user startrather than kill-and-nohup.Its observation plane was not changed with it. Two checks still assume the macOS
nohup-era shape. They fail in opposite directions, which is why neither is obvious:
fleetd/fleetd.outfor the boot linespgrep -f 'target/fleetd.jar'to count daemonsA check that cannot run and a check that ran and found nothing produce the same shape on the
screen. That is the recurring form this repo keeps hitting (#497, #506, and the zero-match rule).
Instance 1 — the log source, reported by the fleet01 lead
Measured on fleet01 on 2026-09-19, during a real redeploy that otherwise succeeded (exit 0, jar
b92db8f42b1a→f90c14e0168a, old pid 1696852 stopped through systemd, new pid 3148354,/healthz200). I did not run this myself; the fleet01 lead did and quoted the output:Three checks then reported themselves unable to answer:
fleetd listeningline after the restart"Under systemd the daemon's stdout goes to the journal, so that file never exists. The first two
were answerable the whole time, from
journalctl --user -u fleetd:The third one splits the other way, and this is the part worth keeping. The drain line is
absent from the journal too. So that is a genuine absence, and the script's own first candidate
explanation — "that daemon predates #522's drain-complete line" — is almost certainly correct,
because the old jar was built 2026-09-10 from
4887731. Two instrument artifacts and one realnegative, arriving in one identical-looking block of three.
The script is already better than most here: it says in so many words that this is not a pass
and not a failure. It still offered two explanations that were both wrong for two of the three,
because "the log file is not where I am looking" is not in its list of explanations.
Instance 2 — the pid count, and why it did not reproduce on the Mac
The fleet01 lead reported
pgrep -fc 'fleetd.jar'returning 2 on their host, with the secondmatch being their own shell, and warned that trap 8 may share the flaw.
running_pidis exactlythat shape (
scripts/redeploy-fleetd.sh:172,PATTERN='target/fleetd.jar'at:79):and
assert_single_daemon(:504) dies when it counts more than one.I tried to reproduce it on the Mac and could not, which is a result worth recording rather
than hiding:
Two reasons, both platform facts and neither a reason to close this:
pgrep -cdoes not exist on BSD/macOS. The count form the fleet01 lead used isLinux-only, so the two hosts cannot even run the same command.
bash -c "<single command>"execs the command in place, so no parent shell survives holdingthe pattern in its argv. An interactive shell, a pipeline, or an
ssh host "…"wrapper doesleave one, and that is where the self-match appears.
So the defect is real on Linux and latent on macOS, and the script is now run on both. The
consequence is worse than the log one: a self-match makes
assert_single_daemondie with"more than one fleetd process is running after this restart" on a redeploy that worked.
There is a third copy of the flaw in the advice text itself (
:510). The die message tells theoperator to "Investigate with
pgrep -f \"$PATTERN\"" — typed by hand, over ssh, that is exactlythe self-matching invocation, so the guidance reproduces the false reading it is meant to resolve.
Fix direction
systemctl --user show fleetd -p MainPIDunder systemd,
launchctlunder launchd, and confirm with/proc/<pid>/exe(Linux) — allself-match-proof. Keep a pattern only for the unsupervised case, and say in the message that a
pattern can match the caller.
journalctl --user -u fleetd, not a file path derived from the repo.fleet01 measured
systemd --user unit installed: fleetd/supervisor detected: systemd.Only the launchd warning is expected there, and it is structural on Linux.
Acceptance criteria
Properties under a change, not the presence of a construct.
does not expect and the run must report "I could not read the log", naming the source it tried
— distinct from "I read the log and the line was absent". Today both print the same thing.
absent, the run must still say so, and must not attribute it to a missing file.
contains the pattern (
ssh host "…", or a non-exec'ing shell) and it must still return thetrue daemon count. A test that only runs it from a plain script stays green while the defect is
present.
assert_single_daemonmust die.Fixing the false positive must not remove the check.
Reported by the fleet01 lead; instances 1 and 3 are their measurement, instance 2's macOS
non-reproduction is mine. Related: #492 (supervisor-aware control plane), #504 (four places a
failed command reads as a clean result), #497 (a sentinel that conflates "no" with "cannot tell").
CORRECTION 1 — two holes in the PR #597 fix. Both close with one change.
Reviewed at the merge gate. The approach is sound, the reproduction is genuine, and breaking the fix on purpose to watch the test go red is exactly the right evidence. Two defects remain, and they are the same shape as the defect being fixed.
The current fix
running_pid()keeps every pidpgrep -f "$PATTERN"returns, except those whoseps -o comm=names a shell:Hole 1 — an exited pid is counted
pgreplists a pid, the process exits, thenps -o comm= -p "$pid"returns nothing.|| truemakescommempty. An empty string matches no shell name, socontinuenever fires and the dead pid is counted.Measured:
That inflates the count and can trip
assert_single_daemonwith a "racing supervisor" death for a process that is already gone. It is the same false positive the ticket exists to remove, just rarer.Hole 2 — the exclusion is a denylist, and the daemon has a name
The list covers
sh bash zsh dash ksh. Anything else that carries the pattern in its argv is counted:sshmatters most, because the ticket names ssh as a live route. A denylist of wrappers is a list someone finds a gap in. And we do not need one, because the thing we are looking for has a fixed name.Measured on the live daemon, names only:
The PR's own report states the premise — "The daemon is always
java" — and then implements the weaker form.The fix: allowlist, not denylist
Count a pid only when its
commisjava. That closes hole 2 by construction and closes hole 1 for free, because an emptycommis notjavaeither.The objection to answer, because it is real
An allowlist can under-count: if fleetd ever stops being launched by
java— a native image, a renamed launcher —running_pid()returns nothing andassert_single_daemonstops noticing a second daemon. That is a false negative, and for a guard that is the worse direction.I do not think it blocks the change, because the script already carries the same assumption one line up:
PATTERN='target/fleetd.jar'. A jar is run byjava. If that stops being true, the pattern breaks before the allowlist does. The allowlist adds no assumption that is not already load-bearing.But say it in the comment rather than leaving it implicit, and name
PATTERNas the sibling assumption, so whoever changes the launch method finds both.Acceptance for the follow-up
commis notjava— including an exited pid, and including a non-shell wrapper such assshorperlcarrying the pattern — is not counted. Test at least one non-shell case; a test that only covers shells passes today and would have passed before this correction.java-named process matching the pattern is still counted, soassert_single_daemonstill fails on a genuine second daemon. Theexec -astandin in the PR is the right technique — point it at ajava-named standin.Out of scope, and correctly left alone
Instance 1 (the
fleetd.outlog source) stays untouched — it needs a systemd host. The peer fleet runssystemd --userand has confirmedfleetd.outis empty there; that half is going to them.The deliberate choice not to wire pid counting to
SUPERVISOR_KIND— becauseOLD_PIDis computed before the supervisor is detected, so it would have meant reordering the main flow — was the right call for this scope, and saying so in the report rather than silently narrowing was the right way to handle it.