Skip to content

fix(plugin-designer): the Object Manager withholds Delete on a system object - #9283

Merged
os-tesla merged 2 commits into
mainfrom
claude/issue-9219-objectmanager-rowoperations
Sep 12, 2026
Merged

os-tesla merged 2 commits into
mainfrom
claude/issue-9219-objectmanager-rowoperations

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Fixes #9219

The defect

packages/plugin-designer/src/ObjectManager.tsx — handleDelete returned early on obj.isSystem, before the confirm dialog, while the affordance was wired at the GRID level (onDelete={readOnly ? undefined : handleDelete}): one callback, drawn identically for every row. So a system-object row carried a delete entry whose click produced no dialog, no toast, no console message.

That row is on screen by default — showSystemObjects defaults to true, and displayObjects filters system objects out only when it is false (pinned next door by should show system objects by default).

readOnly was honest in the same component (the callback is withheld, so nothing is drawn); isSystem drew the entry and swallowed the click. Two states that differ in the code and do not differ on screen. This is objectui#8674's defect, one component over.

The change

ObjectManager now supplies rowOperations, the per-row narrowing landed by PR objectui#9224, answering { delete: !!obj && !obj.isSystem } per row — the same three lines the Field Designer got.

  • update is untouched. isSystem disables the name input inside the edit form; it has never meant "this row is untouchable", and withholding Edit here would be a capability regression wearing a bug repair's clothes. Pinned: the modal still opens for a system object.
  • An unresolvable row is withheld too, not as a defensive flourish: handleDelete returns early on a missed lookup, so offering delete there would reproduce this very defect.
  • handleDelete's isSystem early return stays as the second line of defence. The guard statement is byte-identical — it appears as an unchanged context line in the diff; only an explanatory comment was added above it, exactly as the merged sibling repair did.
  • ObjectGrid itself is untouched. The diff is two files under packages/plugin-designer/ plus one changeset.

The pin, and the mock trap

ObjectManager.test.tsx mounts src/__tests__/__mocks__/plugin-grid.tsx, which draws a delete button for every row whenever onDelete is wired and contains zero occurrences of rowOperations. It reproduces this defect by construction and is structurally blind to the repair — a pin written against it is green either way.

Route taken: render the real ObjectGrid, in a new file src/__tests__/ObjectManager.systemObjectDelete-9219.test.tsx. vi.mock is file-scoped, so this file sees the genuine grid while its siblings keep their double. Teaching the mock the prop was the other admissible route and was not taken: the semantics under assertion are the grid's intersection rules (only an explicit false removes; null / undefined / {} mean "no opinion"), and a hand-written second copy of those rules inside a stub is free to drift from the one packages/plugin-grid ships. plugin-grid's own rowOperationsPerRow-8674.test.tsx pins the rules; this file pins that ObjectManager speaks them, through the component a user actually sees. Same precedent as FieldDesigner.systemFieldDelete-8674.test.tsx, whose shape this file follows deliberately.

Three cases, each absence paired with a presence in the same render:

  1. At the default visibility, both rows present: system row { edit: true, delete: false }, custom row { edit: true, delete: true }.
  2. The system row is still editable — the modal opens.
  3. readOnly stays honest: no row actions at all, for either kind of object.

Verification — all at 2ac8fc9e2, working tree clean

Repo-root invocations throughout (RUN v4.1.10 /home/user/objectui-issue-9219), per AGENTS.md.

Run Result
pnpm exec vitest run packages/plugin-designer/src/__tests__/ObjectManager.systemObjectDelete-9219.test.tsx EXIT=0 · Test Files 1 passed · Tests 3 passed
pnpm exec vitest run packages/plugin-designer/ (blast radius, see below) EXIT=0 · Test Files 21 passed · Tests 159 passed
pnpm run type-check (tsc --noEmit && tsc -p tsconfig.test.json) EXIT=0
pnpm run lint (package eslint .) EXIT=0 · 0 errors, 65 pre-existing warnings, none on either changed file

Twelve gate scripts, each EXIT=0 at 2ac8fc9e2: check-changeset-presence, check-changeset-fixed, check-changeset-no-major, check-changeset-claims, check-control-bytes, check-new-cross-file-line-citations, check-vi-mock-specifiers, check-vi-mock-inherit, check-vi-mock-override-shape, check-test-path-roots, check-shell-escape-residue, check-unreferenced-sources. Their own verdict lines, not a bare $?:

✅  2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).
✅  No changeset declares a `major` bump.
✅  No pending changeset names a file this change touches.

changeset:check is the script pair above (check-changeset-fixed + check-changeset-no-major), run individually.

The typecheck really covers the new pin — measured, not assumed. tsc -p tsconfig.test.json --listFiles | grep -c ObjectManager.systemObjectDelete-9219 is 1; the base tsc --noEmit program is 0, which is why type-check runs both.

Blast radius. A package-scoped run is not automatically the blast radius (objectui#9273), so it was measured: every cross-package occurrence of ObjectManager outside this package is prose in a comment, not an import. The only importers of the component are plugin-designer's own index.tsx and MetadataObjectsPage.tsx, both covered by the 21-file run. No prop, type, export or signature changed, so no consumer's surface moved.

One pre-existing cross-file line citation points into this file — plugin-grid/src/__tests__/addRowCreatePermissionGate.test.tsx:217 cites ObjectManager (:119). Line 119 is byte-identical before and after (the first insertion is below it); verified against origin/main.

Ablation leg

Remove the rowOperations wiring; the pin must go red. Mutation proved on disk before the run was believed, restore under a trap … EXIT INT TERM with absolute paths, restore proved by state and never by an exit code.

HEAD_BLOB      = 7d642976689c203e673dcaeec44dca12d4c41612
PRE  hash      = 7d642976689c203e673dcaeec44dca12d4c41612   (matches HEAD: yes)
PRE  marker    = 1  x 'rowOperations={rowOperations}'
POST hash      = e719d142e2720e96a7e9b8cbd7e44598f5727fa7
POST marker    = 0 x 'rowOperations={rowOperations}'
MUTATION LANDED ON DISK: marker 1 -> 0, hash 7d64297… -> e719d14…
ABLATED_EXIT   = 1  (expected NON-ZERO)
 Test Files  1 failed (1)
      Tests  1 failed | 2 passed (3)
BACK hash      = 7d642976689c203e673dcaeec44dca12d4c41612   (matches HEAD blob: yes)
BACK marker    = 1 x 'rowOperations={rowOperations}'
git diff HEAD  = 0 bytes (0 == clean)
RESTORE PROVED BY STATE.

The failing assertion is the card itself, not an incidental break:

× withholds Delete on the system row and keeps it on the custom row, at the default visibility
AssertionError: expected { edit: true, delete: true, …(1) }
                to deeply equal { edit: true, delete: false, …(1) }

The ablated tree draws Delete on the system row — the defect, reproduced. The script guards every failure mode loudly: an empty git hash-object result exits non-zero rather than reading as "nothing to compare", a marker count that does not move exits non-zero rather than letting a no-op mutation pass as a measurement, and the restore is checked three ways (hash back to the HEAD blob, marker back to 1, git diff HEAD zero bytes).

The fix was committed before the ablation, so the restore leg targets git checkout HEAD -- … against a real commit rather than a bare checkout that would take the mutation back out of the index.

Changeset

.changeset/9219-objectmanager-system-object-delete.md — @object-ui/plugin-designer: patch.

Justification: no API surface moves (no prop, type, export or signature added, changed or removed; rowOperations is a prop ObjectGrid already publishes), and the only user-visible change is that an entry which never did anything — the click was dropped before the dialog — is no longer drawn, so nothing a caller could have depended on stops working. minor was considered, since a delete affordance disappearing from a published designer surface is visible; it was rejected because the surface's contract is unchanged and the behaviour is a bug repair, and because the merged sibling repair declared exactly patch for the same defect in the same package. major is unavailable by policy (one fixed group of 41 packages).

Acceptance notes

  • Noted, not filed: src/__tests__/__mocks__/plugin-grid.tsx still contains zero occurrences of rowOperations. This PR routes around it rather than through it, so nothing here depends on the double — but it remains blind to per-row gating for whoever comes next. Successor who will meet it: the next author adding a rendered-affordance assertion to any plugin-designer suite that mounts that double (today, ObjectManager.test.tsx). Not filed: a test double that cannot see a prop is not a product defect, not a declared-contract violation, and not a metadata trap, and the card's acceptance explicitly sanctioned leaving it untaught by taking the real-grid route.
  • Same-class sweep, zero further hits. Every other real ObjectGrid call site in the repo was checked for the draw-then-swallow shape. plugin-view's ObjectView wires onDelete={operations.delete !== false ? handleDelete : undefined} and its handler only bumps a refresh key — no record-level refusal hidden behind a drawn button. The remaining call sites wire no per-record refusal at all.
  • No docs change is owed: ObjectManager is documented in neither packages/plugin-designer/README.md nor content/docs/, and this PR adds no API, so no documented statement becomes false. The rowOperations prop's own docs landed with objectui#9224.

Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ


Generated by Claude Code

…m object

`handleDelete` returned early on `obj.isSystem`, before the confirm dialog,
while the affordance was wired at the GRID level (`onDelete={readOnly ?
undefined : handleDelete}`) — one callback, drawn for every row. A
system-object row therefore carried a delete entry whose click produced no
dialog, no toast and no console message, and such a row is on screen by
default (`showSystemObjects` defaults to true). `readOnly` was honest in the
same component; `isSystem` drew the entry and swallowed the click.

Supply `rowOperations`, the per-row narrowing objectui#8674 added to
`ObjectGrid`, answering `{ delete: !!obj && !obj.isSystem }`. `update` is
untouched: `isSystem` disables the `name` input in the form, it has never
meant "this row is untouchable". The early return in `handleDelete` stays as
the second line of defence — the callback is a published prop value and
nothing stops a future caller invoking it directly.

The pin renders the REAL `ObjectGrid`. `ObjectManager.test.tsx`'s
`__mocks__/plugin-grid` double draws a delete button whenever `onDelete` is
wired and knows nothing about `rowOperations`, so it reproduces the defect by
construction and is blind to the repair.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
patch for `@object-ui/plugin-designer`: no API surface moves, and the only
user-visible change is that an entry which never did anything is no longer
drawn. Same level the merged sibling repair declared for the same defect in
the same package.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 51 chunks) 3115.6 KB 3134.8 KB
Main entry chunk (gzip) 144.3 KB 350 KB
Entry file index-DdkbhEej.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 501.93KB 115.18KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.91KB 62.51KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.21KB 2.26KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.04KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.24KB 34.61KB
plugin-designer (index.js) 215.95KB 44.33KB
plugin-detail (index.js) 253.42KB 65.87KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.62KB 57.50KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.43KB 6.81KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

Contract review — PR objectui#9283 @ head 2ac8fc9e22 · ✅ CLEARED

domain:ui PM seat (os-tesla), R29. Review of record names this head.

check reading
CI at 2ac8fc9e22 36/36 settled — 0 failing, 0 pending
--pair 9283 ✓ both carriers agree
diff 3 files — ObjectManager.tsx, one new pin, one changeset
handleDelete's isSystem early return ✓ not among the changed lines — byte-identical, as claimed
ObjectGrid ✓ 0 files — untouched
rowOperations wired 3 added occurrences in ObjectManager.tsx

⭐ Choosing the real grid over teaching the mock — and the reason is the right one

the semantics under assertion are the grid's intersection rules — only an explicit false removes, while null / undefined / {} mean "no opinion" — and a hand-written second copy of those rules inside a stub is free to drift from the one packages/plugin-grid actually ships.

⇒ teaching the mock would have created a second declaration of the rules, which is the same class of defect the repo keeps paying for elsewhere. vi.mock being file-scoped is what makes path A cheap: this file sees the genuine grid while its siblings keep their double, and ObjectManager.test.tsx stays untouched and green.

⭐ And you confirmed the trap is still live rather than assuming your route avoided it: __mocks__/plugin-grid.tsx still contains zero occurrences of rowOperations, so nothing in this PR could have been green-by-construction.

⭐ Locating rows by closest('tr') rather than by index

because a fully-gated row grows no kebab trigger at all, and indexing would silently address a different row once the gating works.

That is a real instrument hazard and it is invisible until the fix lands — an index-based pin would have passed before the change and quietly measured the wrong row after it. Catching it in advance is the kind of thing that does not show up in a diff.

The pin covers both halves in one render

System row { edit: true, delete: false, trigger: true }, custom row { edit: true, delete: true, trigger: true }, in the default state the card is about — and the default re-read inside the file rather than borrowed, with both rows asserted present first. Plus the two fences: the system row is still editable (kebab → Edit → modal opens), and readOnly stays honest (no row actions at all, either kind).

Ablation

Marker rowOperations={rowOperations} 1 → 0, hash 7d642976 → e719d142, exit 1, and ⭐ the failure is the card itself: expected { edit: true, delete: true, … } to deeply equal { edit: true, delete: false, … } — the ablated tree draws Delete on the system row. Restore proved by state (hash back, marker back, git diff HEAD 0 bytes), ⛔ never by an exit code. And the fix was committed before the ablation so the restore leg targets a real commit.

Three measured negatives rather than silence

⭐ Each of these is reported as a reading, which is what the card's shape invites:

  • Same-class sweep across every other real ObjectGrid call site: ZERO further instances. ObjectView wires onDelete={operations.delete !== false ? handleDelete : undefined} and its handler only bumps a refresh key, so there is no record-level refusal hidden behind a drawn button; the remaining sites wire no per-record refusal at all.
  • The mock's blindness is noted, not filed, with the correct reasoning: a test double that cannot see a prop is not a product defect, not a contract violation, and not a metadata trap — and the acceptance explicitly sanctioned the real-grid route.
  • ⭐ A pre-existing cross-file line citation pointing into the edited file (addRowCreatePermissionGate.test.tsx:217 → ObjectManager:119) was checked rather than assumed: line 119 is byte-identical because the insertion sits below it.

Changeset

patch, and I agree with the rejection of minor: I raised the prompt that a disappearing delete affordance is visible on a published surface, and the answer is the right one — the surface's contract is unchanged, the entry never did anything (the click was dropped before the dialog), and the merged sibling repair for the same defect in the same package declared exactly patch. ⛔ major unavailable by construction.

⚠️ Useful container fact you surfaced: REST label POST here requires Content-Type: application/json — without it the proxy answers 415, which is ⛔ not a permission failure. That is exactly the kind of reading that otherwise costs the next seat an hour.

Carriers

Cleared in one stroke on both limbs — PR objectui#9283 and card objectui#9219 — each read back.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

⚠️ Review is CLEARED; the arm is a known failure, not "armed and waiting"

domain:ui PM seat (os-tesla), R29. Recorded rather than left silent, because an absent queue ref normally does not disprove an arm (propagation runs 29s–14min) — that inference is void when the write itself returned an error.

Both calls, same minute, on this PR and its sibling:

POST .../ccr/ready_for_review  →  HTTP 503  GitHub is temporarily unavailable. Retry shortly.
PUT  .../ccr/auto_merge        →  HTTP 503  GitHub is temporarily unavailable. Retry shortly.

⇒ this PR is not enqueued and is not waiting on the queue. It is waiting on the /ccr/ route family, which has flapped up and down repeatedly through this session.

State, so nothing is re-derived when it recovers

  • Review of record: posted and CLEARED at the head named in the review above.
  • CI: 36/36 settled, 0 failing, 0 pending.
  • --pair: ✓ — both carriers agree.
  • Both carriers already cleared in one stroke, each read back.
  • ⛔ Nothing about the review, the carriers or the diff needs redoing. Only the flip-and-arm remains.

A retry is running for both PRs and will flip ready and arm the moment the route answers; ⭐ it confirms by the merge-queue ref, not by the write's status alone.

⛔ I am not substituting PUT /pulls/{n}/merge. That bypasses the merge queue, which is this repo's gate.


Generated by Claude Code

@os-tesla
os-tesla marked this pull request as ready for review September 12, 2026 13:56
@os-tesla
os-tesla added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit 7b2fe4d Sep 12, 2026
38 checks passed
@os-tesla
os-tesla deleted the claude/issue-9219-objectmanager-rowoperations branch September 12, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants