Skip to content

Commit bada8d3

Browse files
fix(pm): fleet-write relay reports created and moved card numbers as check-run annotations; the read-backs read them first (#21312)
Fixes #21294 Clause-②: no ## What this changes Per the triage ruling on #21294 (comment 5944826620, "Ruling: emit annotations"): the relay reports each card it creates or moves on the job's check run, and the read-backs read that first. - **Emission (`scripts/pm/fleet-write/execute.mjs`).** For every action whose op creates or moves a card (`issue_create`, `transfer`; the table is `ANNOTATED_OPS`) and whose request LANDED, the executor prints ONE workflow command to stdout: a `notice` titled `fleet-write OP` whose message is `fleet-write action=I op=OP number=N url=URL`. The number and url come from the platform's ANSWER to that request (the created issue's `number` / `html_url`; the `transferIssue` answer's `issue.number` / `issue.url`), never from the request. No other op emits one. An answer with no number and url, or one whose url names another repository than the op lands on, emits nothing and the log says so. Escaping follows actions/toolkit `command.ts` (`escapeData` / `escapeProperty`). - **One spelling (`scripts/pm/fleet-write/dispatch.mjs`).** `relayAnnotationMessage` (writer) and `parseRelayAnnotation` (reader) sit side by side, and the executor imports both. It emits only a message the parser reads back as exactly that number, url and landing repository. `matchRunAnnotations` keeps a row only when the stroke's action at its index has that op and its url names the repository that op lands on. When one action has two different rows, both are dropped. - Why `dispatch.mjs` and not `execute.mjs` or a new module: a `dispatch.mjs` → `execute.mjs` import closes a static cycle through `label-write.mjs`. A dynamic one, taken while a top-level-await entry such as `label-write.mjs` is still evaluating, is the deadlock shape `dispatch.mjs`'s header records. A new file would sit outside `RELAY_FILES` and outside every self-test gate's derived population (measured: `dispatch-gates --commands` on a new `fleet-write/` path derives none of the `check:pm-fleet-write-*` gates). `execute.mjs` already reaches `dispatch.mjs` through `label-write.mjs`, so its new direct import adds no edge to any cycle. - **The read (`sendFleetWrite`).** After a success run carrying an annotated op, it reads `GET /repos/objectstack-ai/objectstack/actions/runs/{id}/jobs` and then `GET .../check-runs/{id}/annotations`. It matches the rows and prints one line for each row, each ignored row, each annotated action no row names, and an unreadable read. It hands them on as `result.annotations` (and in the CLI's `--json`). A stroke with no annotated op reads nothing extra. - **`issue_create` read-back:** an action an annotation names is read AT that number: one GET, no list, no re-list. Otherwise the `ISSUE_CREATE_RELIST_DELAYS_MS` re-list runs unchanged. If that GET fails, the result is `unread` (exit 6), and the code still does not re-list: the fallback is for an absent annotation only. - **Transfer read-back (`scripts/pm/issue-transfer.mjs`).** Under the relay, the number now comes from the annotation, then the 301, then the title. With the annotated number, the card is read on the target by number and no title search is made. `TRANSFER_READ_BACK_DELAYS_MS` is unchanged, and the transfer is never re-sent. The PM's conditional ruling for a session that cannot read the target, verbatim: > 前提成立 ⇒ 注解等价于 direct 路径里 `transferIssue` 的 `moved` 应答:读回先按注解的号去 GET 目标;目标 200 且同题 ⇒ exit 0;目标 403(本会话未挂该仓)⇒ **仍 exit 0**,打印 `confirmed_by: relay-annotation` 与目标 URL、源 URL 读数作旁证(pending redirect 照印);目标 404 或同题不符 ⇒ 维持现有 exit 4 / 6 判定。 The premise holds: the executor reads the number from the `transferIssue` answer (`data.transferIssue.issue.number` / `.url`), the same object `requestLanded` already holds to the target. Implemented as ruled. A 403 that is an exhausted rate limit is not this exception and stays exit 6. `--json` gains `confirmed_by` (`target` | `relay-annotation`). - **In-place fix, outside the claim's declared surface (`scripts/pm/issue-create.mjs`).** Its relay path listed the target's issues ONCE, after `sendFleetWrite` returned, to find the new number. That single list was protected from the measured list lag only because `sendFleetWrite`'s own read-back had already waited out the re-list. Once that read-back reads the annotated number instead, the protection is gone. The fix: take the number from `sent.annotations` first, and fall back to the existing title list only when no annotation names it. Evidence: the new case "the control — no annotation, the same lagging list — is UNCONFIRMED (6)" beside the annotated case, which exits 0 with zero list reads; ablation 5 below. Same defect family as the card, a mechanical change of the shape the ruling fixed, no open PR touches the file (every open PR's file list read), and its gate `check:pm-issue-create` is already in the dispatch's derived list. **Not touched:** `.github/workflows/fleet-write.yml`. The notice is a workflow command printed by the existing execute step. The measured relay run below already carries annotations from the same job under its `contents: read` grant, so no workflow change is needed. `scripts/pm/dispatch-gates.mjs` is not touched either. ## Measured from this (cloud) container - Relay run 36960568160: its jobs list answers job 110693116713, whose `check_run_url` ends in `/check-runs/110693116713` (job id equals check-run id). `GET /check-runs/110693116713/annotations` answers 200 with 3 annotations: two `Input 'app-id' has been deprecated` warnings and the runner's `ubuntu-latest` notice. There is no relay annotation yet, as expected before this lands. `output.summary` is `null`. The parser is pinned against those two foreign messages. - A repository not attached to this session answers 403 with `documentation_url` on docs.anthropic.com, so `classifyHttp` reads it as `prerequisite`. The 403 exception is therefore keyed on the status and "not an exhausted rate limit", not on `classifyHttp`. - Platform cap: "10 warning annotations, 10 error annotations, and 10 notice annotations per step" and "50 annotations per job" (actions/toolkit `docs/problem-matchers.md`; docs.github.com is not reachable from this container). Past the cap, the later actions carry no annotation and each one falls back on its own. This is stated in both docblocks. - **NOT MEASURED, live:** the relay runs `main`'s `execute.mjs`, so the first live annotation appears on the first relay `issue_create` / `transfer` after this merges. Until then every reader takes its fallback, as ruled. ## Tests (at `4121efc7`) - Self-tests: `check:pm-fleet-write-execute` 83 cases / 12 batteries (+1: annotations, 12 cases). `check:pm-fleet-write-dispatch` 173 / 17 (+1: the run's annotations, 14). `check:pm-issue-transfer` 75 / 9 (+1: the relay annotation, 12; CI does not run this one, see #21295, so it was run here). `check:pm-issue-create` 47 / 7 (+1: the relay annotation, 5). All exit 0, and each battery floor was raised by one. - Pins the ruling names: both read-back batteries gain the annotation case with no re-list or title search, and both keep the fallback case (absent, unreadable, ignored, past the cap). - Gates: the union of the dispatch's 56 named commands, the 37 that `dispatch-gates --commands` derives from the actual 4-path change set (a subset of the 56), and `pnpm check:pm-issue-transfer`. That is 57 commands, every one exit 0. `check:pm-dispatch-gates` ran detached: "dispatch-gates self-test: 1976 cases pass", exit 0. `dispatch-gates --ran`: "37 derived famil(ies) accounted for — 37 run, 0 NOT-MEASURED". `check-self-test-workflow-commands` stays green (230 self-tests; the new self-test output prints no workflow command). - Lint, narrowed: `eslint --no-inline-config --format json` over the 4 files reports 4 files, 0 errors, 0 warnings. Population: `eslint.config.mjs`'s `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` block and `COMMENT_SWALLOW_FILES`. Invariance: the config sets no `parserOptions.project` or `projectService` (no type-aware linting), so this diff cannot move a verdict in an untouched file. - Ablations, each through `scripts/ablation-replace.mjs` (anchor hit 1 → 0, blob changed; restore proven blob == HEAD and `git diff HEAD` empty). Expected direction: red; observed: red every time. 1. `dispatch.mjs` read-back ignores the annotation: 6 of 173 fail. 2. `issue-transfer.mjs` 403 exception removed: 3 of 75 fail. 3. `issue-transfer.mjs` takes no annotation: 8 of 75 fail. 4. `execute.mjs` emission line deleted: 3 of 83 fail. 5. `issue-create.mjs` takes no annotation: 3 of 47 fail. ## Acceptance notes - Every relay run carries two warnings that `actions/create-github-app-token@v3`'s input `app-id` is deprecated ("Use 'client-id' instead"). This is drift in `fleet-write.yml`, a `domain:spec` surface. It is noted here and not filed. Carrier: whoever next edits `fleet-write.yml`. - The claim's declared file surface does not name `scripts/pm/issue-create.mjs` (the in-place fix above). The owning seat may amend it; this run posts no claim. --- _Generated by [Claude Code](https://claude.ai/code/session_01FNKm1SmPpuJASnbjxWGtsJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1caa603 commit bada8d3

4 files changed

Lines changed: 680 additions & 69 deletions

File tree

0 commit comments

Comments
 (0)