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"><?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> </span><span style="color: #DD0000">'error' </span><span style="color: #007700">=> \</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> (string) </span><span style="color: #0000BB">$canvas_page</span><span style="color: #007700">-></span><span style="color: #0000BB">getEntityType</span><span style="color: #007700">()-></span><span style="color: #0000BB">getLabel</span><span style="color: #007700">(),<br> </span><span style="color: #0000BB">$canvas_page</span><span style="color: #007700">-></span><span style="color: #0000BB">id</span><span style="color: #007700">(),<br> ),<br>]));<br></span><span style="color: #0000BB">?></span></span></pre></pre><p>After:</p>
<pre><pre><span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #007700">throw new </span><span style="color: #0000BB">ConflictHttpException</span><span style="color: #007700">(<br> \</span><span style="color: #0000BB">sprintf</span><span style="color: #007700">(<br> </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> (string) </span><span style="color: #0000BB">$canvas_page</span><span style="color: #007700">-></span><span style="color: #0000BB">getEntityType</span><span style="color: #007700">()-></span><span style="color: #0000BB">getLabel</span><span style="color: #007700">(),<br> </span><span style="color: #0000BB">$canvas_page</span><span style="color: #007700">-></span><span style="color: #0000BB">id</span><span style="color: #007700">(),<br> )<br>);<br></span><span style="color: #0000BB">?></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