ApiContentControllers::patch() double-encodes the 409 Conflict error message, producing malformed CLI output
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3588741. --> Reported by: [mglaman](https://www.drupal.org/user/2416470) Related to !1067 >>> <h3 id="overview">Overview</h3> <p>When <code>ApiContentControllers::patch()</code> detects an existing auto-save, it throws a <code>ConflictHttpException</code> with its message wrapped in <code>Json::encode()</code>. <code>ApiExceptionSubscriber</code> then serializes that string into the <code>errors</code> array of the JSON response, resulting in a double-encoded JSON string as the error value:</p> <pre>{"errors": ["{"error":"Canvas Page with ID 42 has existing auto-saved data..."}"]}</pre><p>Canvas CLI's <code>parseCanvasErrors()</code> extracts <code>errors[0]</code> verbatim and surfaces it to the user, so the output is the raw JSON string rather than a readable message.</p> <p>Due to this, <a href="https://git.drupalcode.org/project/canvas/-/blob/1.x/packages/cli/src/services/api.ts?ref_type=heads#L697">https://git.drupalcode.org/project/canvas/-/blob/1.x/packages/cli/src/services/api.ts?ref_type=heads#L697</a> no errors were being returned.</p> <h3 id="proposed-resolution">Proposed resolution</h3> <p>Remove the <code>Json::encode()</code> wrapper in <code>ApiContentControllers::patch()</code> and pass a plain string to <code>ConflictHttpException</code>. <code>ApiExceptionSubscriber</code> serializes it once, producing a clean <code>errors[0]</code> value that <code>parseCanvasErrors()</code> surfaces correctly.</p> <p>Before:</p> <pre><pre><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br></span><span style="color: #007700">throw new </span><span style="color: #0000BB">ConflictHttpException</span><span style="color: #007700">(</span><span style="color: #0000BB">Json</span><span style="color: #007700">::</span><span style="color: #0000BB">encode</span><span style="color: #007700">([<br>&nbsp; </span><span style="color: #DD0000">'error' </span><span style="color: #007700">=&gt; \</span><span style="color: #0000BB">sprintf</span><span style="color: #007700">(</span><span style="color: #DD0000">'%s with ID %s has existing auto-saved data. Please use the UI to publish it or discard it.'</span><span style="color: #007700">,<br>&nbsp;&nbsp;&nbsp; (string) </span><span style="color: #0000BB">$canvas_page</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getEntityType</span><span style="color: #007700">()-&gt;</span><span style="color: #0000BB">getLabel</span><span style="color: #007700">(),<br>&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">$canvas_page</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">id</span><span style="color: #007700">(),<br>&nbsp; ),<br>]));<br></span><span style="color: #0000BB">?&gt;</span></span></pre></pre><p>After:</p> <pre><pre><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br></span><span style="color: #007700">throw new </span><span style="color: #0000BB">ConflictHttpException</span><span style="color: #007700">(<br>&nbsp; \</span><span style="color: #0000BB">sprintf</span><span style="color: #007700">(<br>&nbsp;&nbsp;&nbsp; </span><span style="color: #DD0000">'%s with ID %s has existing auto-saved data. Please use the Canvas UI to publish or discard it before pushing.'</span><span style="color: #007700">,<br>&nbsp;&nbsp;&nbsp; (string) </span><span style="color: #0000BB">$canvas_page</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getEntityType</span><span style="color: #007700">()-&gt;</span><span style="color: #0000BB">getLabel</span><span style="color: #007700">(),<br>&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">$canvas_page</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">id</span><span style="color: #007700">(),<br>&nbsp; )<br>);<br></span><span style="color: #0000BB">?&gt;</span></span></pre></pre><h3 id="ui-changes">User interface changes</h3> <p>None in the Drupal UI. Canvas CLI output changes: a 409 response now produces a readable plain-text error message instead of a raw JSON string.</p>
issue