Found while implementing #4109 (which touches the same route and the same submit path). Filed unassigned for triage; deliberately not fixed in that PR — it is a separate defect with a separate acceptance test, and #4109's scope is the shell nesting plus the post-submit default.
What happens
ActionRunner.executeForm forwards the current record id to the internal form route:
packages/core/src/actions/ActionRunner.ts:1325-1329
const recordId =
(this.context.record && (this.context.record as { id?: unknown }).id) ??
(this.context.data && (this.context.data as { id?: unknown }).id);
const base = `/forms/${name}`;
const to = recordId != null ? `${base}?recordId=${encodeURIComponent(String(recordId))}` : base;
apps/console/src/components/FormPage.tsx never reads that param. The only query params it consumes are the prefill_ ones, in readPrefill. So in internal mode the route:
- loads the FormView metadata and renders empty inputs — the record's current values are never fetched;
- submits through
submitInternal, which is POST /api/v1/data/:object — an INSERT, unconditionally.
An "edit this record" action therefore renders a blank form and, on submit, creates a second record instead of updating the one the user was looking at.
Reproduction (showcase)
showcase_log_time in examples/app-showcase/src/ui/actions/index.ts is exactly this shape — type: 'form', target: 'showcase_task.edit', locations: ['record_header', 'record_related', 'record_section']. Open any Task, click Log Time, fill the form, Submit: a NEW task is created and the original is untouched.
Why it is worth grading now
The forwarded ?recordId= says the producer's intent is an edit, and the target view is literally named edit, so the metadata is not at fault — the renderer simply never implemented the read side. Until #4109 the outcome was invisible: the post-submit default was the anonymous thank-you panel, so the user saw "Your submission has been received" and no record. After #4109 an internal submit lands on the record it created, which means the duplicate becomes visible immediately (arguably an improvement — it surfaces the bug instead of hiding it — but it is still a duplicate).
What a fix would need to decide
Not obvious enough to guess, which is part of why it is filed rather than fixed:
- whether
recordId should make the route load the record and switch the submit to PATCH /api/v1/data/:object/:id, or whether internal type: 'form' should route to the existing app-shell record-form surface instead of this standalone renderer;
- what the route should do when
recordId names a record the user cannot read, or one belonging to a different object than the view's target;
- whether
recordId deserves a place in app-shell/src/urlParams.ts (it is adjacent to the reserved recordId drawer param already registered there, and the collision is worth ruling on explicitly).
Measured on
origin/main at 4cb0562b5, by reading the two files above; not browser-verified.
Generated by Claude Code
Found while implementing #4109 (which touches the same route and the same submit path). Filed unassigned for triage; deliberately not fixed in that PR — it is a separate defect with a separate acceptance test, and #4109's scope is the shell nesting plus the post-submit default.
What happens
ActionRunner.executeFormforwards the current record id to the internal form route:packages/core/src/actions/ActionRunner.ts:1325-1329apps/console/src/components/FormPage.tsxnever reads that param. The only query params it consumes are theprefill_ones, inreadPrefill. So in internal mode the route:submitInternal, which isPOST /api/v1/data/:object— an INSERT, unconditionally.An "edit this record" action therefore renders a blank form and, on submit, creates a second record instead of updating the one the user was looking at.
Reproduction (showcase)
showcase_log_timeinexamples/app-showcase/src/ui/actions/index.tsis exactly this shape —type: 'form',target: 'showcase_task.edit',locations: ['record_header', 'record_related', 'record_section']. Open any Task, click Log Time, fill the form, Submit: a NEW task is created and the original is untouched.Why it is worth grading now
The forwarded
?recordId=says the producer's intent is an edit, and the target view is literally namededit, so the metadata is not at fault — the renderer simply never implemented the read side. Until #4109 the outcome was invisible: the post-submit default was the anonymousthank-youpanel, so the user saw "Your submission has been received" and no record. After #4109 an internal submit lands on the record it created, which means the duplicate becomes visible immediately (arguably an improvement — it surfaces the bug instead of hiding it — but it is still a duplicate).What a fix would need to decide
Not obvious enough to guess, which is part of why it is filed rather than fixed:
recordIdshould make the route load the record and switch the submit toPATCH /api/v1/data/:object/:id, or whether internaltype: 'form'should route to the existing app-shell record-form surface instead of this standalone renderer;recordIdnames a record the user cannot read, or one belonging to a different object than the view's target;recordIddeserves a place inapp-shell/src/urlParams.ts(it is adjacent to the reservedrecordIddrawer param already registered there, and the collision is worth ruling on explicitly).Measured on
origin/mainat4cb0562b5, by reading the two files above; not browser-verified.Generated by Claude Code