tooling-gap: no pr-edit.sh — the wrapper set can edit an issue but cannot edit a PR, so draft/undraft is unreachable through the mandated path #1080
Open
opened 2026-08-06 07:49:05 +00:00 by Mos
·
2 comments
No Branch/Tag Specified
main
next
fix/1138-conditional-federation
feat/webui-p2-data-auth
fix/gateway-runner-image
feat/webui-p1-vite-skeleton
fix/break-c-hooks-and-web-image
docs/webui-fleet-claude-bridge-plan
fix/wizard-gateway-failure
fix/ci-queue-wait-no-status
fix/next-node-gate
fix/mosaic-init-rce
feat/lease-promotion-and-harness-isolation
greenfield/fomo-lin
fix/1099-pipefail-wake
fix/1099-pipefail-tests
fix/1099-pipefail-sweep
fix/framework-shell-portability
fix/1043-pane-git-identity
fix/1081-issue-close-silent-comment-failure
fix/1090-enrollment-wallclock-tolerance
feat/1082-tea-stale-token-diagnostic
fix/detect-platform-silent-128-outside-repo
feat/1050-install-state-machine-red-fixture
fix/pr-merge-message-field
feat/1051-mosaic-brain-installer
feat/1045-mosaic-cred
remediation/state
fix/1056-upgrade-rollback-control-race
fix/1019-ci-queue-timeout-harness
feat/rm-02-gate-registry
fix/rm-01-reproducible-checkout
remediation/mission-setup
fix/hygiene-inert-format-gate
fix/1019-queue-guard-stdin
feat/mos-ste-writing-standard
fix/1007-suite-hermeticity
fix/991-comment-url-scheme-normalise
feat/push-guard-null-case-verification
mos-comms-live
docs/heartbeat-framework-layering-ms-lead
feat/869-c4-version-coupling
feat/869-c2-install-ordering-guard
feat/869-c5-doctor-activation-check
feat/per-agent-gitea-identity
fix/875-belongs-case-insensitive-slug
fix/ci-queue-wait-404-branch-absent
feat/869-c1-activation-probe
feat/869-c3-broker-supervisor
fix/865-tea-cli-comment-invocation
feat/glpi-skills
fix/860-deflake-mutator-lease-gate
fix/850-detect-platform-port-normalization
fix/856-worktree-deps-preflight
fix/835-pr-review-approve-reject-comment-flag
fix/848-truthful-evidence
fix/812-pr-review-comment
fix/849-recovery-runtime-fixture-race
docs/758-ledger-m5-001-sync
feat/834-tc-server-side-doc
feat/833-constrained-recovery-command
feat/827-gate0-probe
governance/gate0-probe3-amendment
fix/795-codex-pr-diff
fix/795-ci-base-jq
fix/795-ci-base-git
feat/791-pr3-fleet-regen
feat/791-pr2-snapshot-restore
fix/807-glpi-206
fix/808-agent-send-false-sender
feat/791-upgrade-config-protection
feat/790-mosaic-yolo-claudex-pr2
feat/790-mosaic-yolo-claudex
feat/758-v1-v2-migrator
fix/766-exact-fleet-comms
test/758-reconciler-lifecycle-gates
docs/771-kbn101-db-role-split
test/758-example-profile-dispositions
feat/758-shared-role-resolution
feat/mos-logical-identity-fencing
feat/769-kbn100-unified-schema
docs/753-kbn010-threat-gate
feat/758-roster-v2-compiler
feat/756-official-discord-plugin
docs/758-fleet-config-management
fix/mos-option2-qualification-format
docs/issue-758-m0
docs/mos-option2-qualification
mos-comms
feat/tess-interaction-agent
fix/tess-docs-format
draft/mosaic-platform-prd
fix/installer-provider-gate-and-local-gateway-redis
release/mosaic-cli-0.0.37
feat/framework-constitution-alpha
fix/git-wrapper-repo-detection
fix/woodpecker-wrapper-legacy-mosaic
fix/t-a292e96f-gitea-pr-metadata
fix/gitea-pr-metadata-login-t-a292e96f
fix/t_a292e96f-pr-metadata-gitea
fix/t_3a368a52-gitea-usc-login
fix/bootstrap-hotfix
fix/populate-known-packages-list
fix/idempotent-init
v0.0.39-alpha
mosaic-v0.0.31
fed-v0.2.0-m2
fed-v0.1.0-m1
mosaic-v0.0.29
mosaic-v0.0.28
mosaic-v0.0.27
mosaic-v0.0.26
mosaic-v0.0.25
mosaic-v0.0.24
v0.2.0
v0.1.0
v0.0.8
v0.0.7
v0.0.6
v0.0.5
v0.0.4
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mosaicstack/stack#1080
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.
The sanctioned wrapper set can edit an issue and cannot edit a pull request.
issue-edit.shexists; there is nopr-edit.sh.pr-create.shsupports--draftat creation only — nothing converts an existing PR to or from draft.Measured
Why it matters, from a case that just occurred
A PR can reach a state where every provider-visible gate reads GO — current approvals, terminal-green CI,
mergeable=True— while a real blocker exists that lives outside the provider entirely (a tool version on the executing host, a deployment gap, an unmet requirement raised out of band).In that state, a merge executor consulting only the API merges correctly by every signal available to it, and does the wrong thing. The correct disposal is to put the hold on the surface the merger reads, and draft status is the right instrument for it:
mergeableBecause no wrapper offers it, the strongest hold reachable through the mandated path is a
request-changesreview from the merge executor. That works, and it is strictly worse in two measurable ways:mergeable=Trueuntouched. Verified on both affected PRs after the hold was placed: the reviews list carries it, and the flag a naive merger might consult still readsTrue. Draft would have moved that field;request-changesonly narrows the gap.Requested
pr-edit.shsupporting at minimum--draft/--readyon an existing PR, alongside the title/body edit capability the issue side already has.Why this is filed rather than worked around
The action is one raw API call away. Doing it that way would have produced the better outcome by the worse route — bypassing the mandated wrapper layer, on a host that certifies merges, against a PR belonging to another author. A gap in the sanctioned tooling is a thing to report, not a licence to route around it. The weaker hold was used and the gap is recorded here instead.
Scope note
Nothing above depends on a particular deployment, host, or operator: it is a statement about which verbs the
git/wrapper set exposes for PRs versus issues, and a documented consequence of the missing one.No closing keywords intended; none used.
Measured evidence, one hour after filing: the predicted failure occurred, with a clean control.
This issue argued that
request-changesis a strictly worse hold than draft. That was an argument when filed. It is now an observation, and the discriminating control was supplied by ordinary work rather than by a test.Same instrument, two PRs, one variable
One variable — whether the head moved — and opposite outcomes. A
request-changeshold does not survive a head move. A draft flag would have.The failure runs in the worst direction
PR A did not merely lose its hold. It came to read clean: no blocking review, no approvals,
mergeable=True. That is a weaker signal than the two approvals it replaced — a merger consulting the provider saw a PR with nothing objecting to it. The hold had to be manually re-placed at the new head, and it will expire again on the next push.So the cost of the missing capability is not "the hold is less elegant." It is:
Draft status has none of these properties: it survives head moves, cannot be confused with a review verdict, and is cleared only by a deliberate act.
Restating the request with the evidence attached
pr-edit.shsupporting--draft/--readyon an existing PR. The workaround available through the mandated path expired on its own within an hour of being placed, and required a human-noticed catch to restore — which is precisely the class of thing a hold exists to not depend on.No closing keywords intended; none used.
The structural reason, read off the API schema rather than inferred from behaviour — and it reframes the request: the two axes invert in the middle, so no available fallback is adequate on both.
A review is a statement about a commit. A comment is a statement about the PR. That single difference produces the observed failure and the ordering below.
Strength:
draft>REQUEST_CHANGES> comment. Durability:draft> comment >REQUEST_CHANGES. The middle term swaps.⇒ So the gap is not "no draft toggle." It is that the sanctioned set offers no hold that is both machine-readable and durable — and a merge executor consulting the provider reads exactly the machine-readable layer that expires. Each fallback sacrifices a different axis;
draftis the only instrument adequate on both.The cost, stated as a recurring number
Re-placing the hold at the new head is correct and it will expire on the next push, and the one after that. The cost of the missing capability is not one re-placement. It is:
one manual re-placement per push, forever, and silently — with a window after each push in which the PR reads clean, because the approvals staled by the same event leave nothing objecting to it.
That window closes only if a human or another seat happens to notice. On the occurrence that prompted this comment, it was noticed by a lane lead performing an unrelated verification, not by anything in the tooling.
Note on how this was demonstrated
The gap was demonstrated by obeying the wrapper mandate rather than bypassing it: the weaker sanctioned instrument was used, its weakness was stated in advance, and it failed as predicted within the hour. A gap shown this way costs one expired hold; showing it by reaching for the raw API would have hidden it and left an undocumented divergence on a host that certifies merges.
No closing keywords intended; none used.