Repository navigation
Commit cc2e951
fix(pm): fleet-write fails closed on an indeterminate liveness read, reads it only behind the proxy re-exec, and stops reading a proxy 403 as a rate limit (#19780)
Fixes #19774
## What changed
`scripts/pm/**` only — the fleet's own write tools, not on the governed
register.
1. **The liveness read runs only behind the proxy.** `relayLive()` in
`fleet-write/dispatch.mjs` refuses to read from a process whose fetch
would bypass `HTTPS_PROXY` (a proxy configured, no `--use-env-proxy` on
the process): the reading is indeterminate, names the flag, and is not
cached, so the re-exec'd child reads for itself. `post-stamped.mjs` now
re-execs on `--dry-run` too (a dry run resolves the route, and the
route's read is a request); `label-write`, `issue-create`, `close-cards`
and `with-fleet.sh` already re-exec before resolving, and `--route` did.
The ordering is pinned in post-stamped's self-test and the unrouted
refusal in dispatch's.
2. **Fail closed on an indeterminate read.** `relayLive()` judges three
buckets: 200 with `state === 'active'` is live (dispatch); the file's
404 and the `disabled_manually` state are the two DEFINITE not-live
signals and still turn a cloud seat direct with the printed line; any
other status (401, 403, 5xx), a 200 without a readable state, any other
state, or no answer is INDETERMINATE, and `selectTransport` /
`resolveRoute` refuse with exit 3 naming the status and the read — under
`auto` and explicit `OS_FLEET_TRANSPORT=dispatch` alike, never `direct`,
never silent. The refusal carries the session so a tool can say who was
refused. Explicit `OS_FLEET_TRANSPORT=direct` remains the only way to
write as the personal account in a cloud container, and the tool prints
it.
3. **A proxy refusal is not a rate limit.** `classifyHttp`
(label-write.mjs) no longer reads an absent `x-ratelimit-remaining`
header as zero (`Number(null)` is `0`, which is how the mint's proxy 403
wrote a 30-minute STOP MARKER), and recognises the egress proxy's 403
body — `isProxyRefusal()` in write-pace.mjs: the message naming the
proxy, or a `documentation_url` that is not GitHub's, the measured
discriminant — as a route failure (`prerequisite`). `stopSignalFrom`
writes no marker for such a body whatever verdict or header accompanies
it; `noteResponse` gives back the slot `paceWrite` took (no budget
spent) and prints one line; `fleet-token` names the proxy and the relay
spelling in its refusal. Callers (dispatch.mjs, fleet-token.mjs) now
hand the body to the classifier. Existing markers are not migrated; they
expire.
4. **Unchanged:** ceilings, the size route, the op table, the executor,
the workflow, every allow rule. The documented spellings that described
the fall-back (dispatch.mjs header, with-fleet.sh header) now say when
the tool refuses instead.
## Verification record
**Live, in this cloud container, with NO `OS_FLEET_SESSION` set**
(`origin/main` at 2005a55, worktree at 25ee9c2):
```
$ node scripts/pm/fleet-write/dispatch.mjs --route
ℹ️ re-exec with --use-env-proxy: HTTPS_PROXY is set (http://127.0.0.1:43969) and node's fetch does not read it.
{"requested":"auto","transport":"dispatch","failed":null,"session":"session_01GnJon4xkRvphn4w28xx3An","session_source":"CLAUDE_CODE_REMOTE_SESSION_ID","reason":"OS_FLEET_TRANSPORT is auto → dispatch: …"}
exit 0
```
A bare `node scripts/pm/post-stamped.mjs --comment=19774 --file=…
--dry-run` (no proxy flag on the command) now prints the re-exec line
and then `post-stamped: transport dispatch — …` with the derived session
and `state active`; on `main` the same command read condition ③ as `HTTP
401` and reported `direct`. The forced-401 case is proven in the
dispatch self-test's fake platform (exit-3 shaped refusal naming HTTP
401 under auto and under explicit dispatch), and `--route` behind a
proxy that answers nothing is proven end to end (real re-exec, exit 3,
error naming no answer).
**Batteries, each run alone with its exit captured before any pipe:**
| command | exit | verdict line |
|---|---|---|
| `pnpm check:pm-fleet-write-validate` | 0 | 69 cases pass across 7
batteries |
| `pnpm check:pm-fleet-write-execute` | 0 | 48 cases pass across 9
batteries |
| `pnpm check:pm-fleet-write-dispatch` | 0 | 101 cases pass across 11
batteries (was 90 across 10) |
| `pnpm check:pm-with-fleet` | 0 | 32 cases pass |
| `pnpm check:pm-post-stamped` | 0 | 610 cases pass across 21 batteries
(was 609) |
| `pnpm check:pm-label-write` | 0 | 91 cases pass across 10 batteries
(was 88) |
| `pnpm check:pm-issue-create` | 0 | 42 cases pass across 6 batteries |
| `pnpm check:pm-close-cards` | 0 | 111 cases pass across 12 batteries |
| `pnpm check:pm-write-pace` | 0 | 113 cases pass across 12 batteries
(was 109) |
| `pnpm check:pm-fleet-token` | 0 | 75 cases pass across 8 batteries
(was 73) |
`npx eslint --no-inline-config` on the five changed `.mjs` files: exit
0.
**Gates** — `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` on the edited tree derived 39
families (6 paths vs merge base 2005a55; 355 changed lines, under the
human-merge threshold). Each was run alone with its exit code captured
before any pipe; all 39 exited 0. `--ran` with the codes recorded: `39
derived famil(ies) accounted for — 39 run, 0 NOT-MEASURED (a DERIVED
zero — all 39 recorded an exit code and none of them is 3)`. Repo-wide
`pnpm lint` is CI's run; the changed files were linted directly (above).
## Changeset
None: `scripts/pm/**` publishes nothing from any released package, so
`skip-changeset` applies. This dispatch forbids label writes, so the
label is the seat's to apply.
## Acceptance notes
- `disabled_inactivity` (GitHub disabling a workflow for inactivity) is
treated as indeterminate, not as a definite not-live signal: the card
names exactly two definite signals, and this one is neither the
maintainer's switch nor an absent file. If the seat wants it definite,
it is a one-line change in `relayLive` plus its pin.
- `.claude/skills/pm-dispatch/references/rest-channel.md` and
`platform-readings.md` still describe the previous fall-back on any
non-200 read; governed paths, outside this card's `scripts/pm/**` scope.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01GnJon4xkRvphn4w28xx3An)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 531689c commit cc2e951
6 files changed
Lines changed: 282 additions & 73 deletions
File tree
- scripts/pm
- fleet-write
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
123 | 123 | | |
124 | 124 | | |
125 | 125 | | |
126 | | - | |
| 126 | + | |
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
| |||
457 | 457 | | |
458 | 458 | | |
459 | 459 | | |
460 | | - | |
461 | | - | |
| 460 | + | |
| 461 | + | |
462 | 462 | | |
463 | 463 | | |
464 | 464 | | |
| |||
542 | 542 | | |
543 | 543 | | |
544 | 544 | | |
545 | | - | |
| 545 | + | |
546 | 546 | | |
547 | 547 | | |
548 | 548 | | |
549 | 549 | | |
550 | 550 | | |
551 | 551 | | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
552 | 555 | | |
553 | 556 | | |
554 | | - | |
| 557 | + | |
555 | 558 | | |
556 | 559 | | |
557 | 560 | | |
| |||
635 | 638 | | |
636 | 639 | | |
637 | 640 | | |
638 | | - | |
| 641 | + | |
639 | 642 | | |
640 | 643 | | |
641 | 644 | | |
| |||
794 | 797 | | |
795 | 798 | | |
796 | 799 | | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
797 | 807 | | |
798 | 808 | | |
799 | 809 | | |
| |||
0 commit comments