diff --git a/.github/workflows/test-ai-inference-github-models.lock.yml b/.github/workflows/test-ai-inference-github-models.lock.yml index be4965a0d4..ac23d5291b 100644 --- a/.github/workflows/test-ai-inference-github-models.lock.yml +++ b/.github/workflows/test-ai-inference-github-models.lock.yml @@ -694,6 +694,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-claude-add-issue-comment.lock.yml b/.github/workflows/test-claude-add-issue-comment.lock.yml index 1acbc1b956..95e9d4bcb7 100644 --- a/.github/workflows/test-claude-add-issue-comment.lock.yml +++ b/.github/workflows/test-claude-add-issue-comment.lock.yml @@ -881,6 +881,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-claude-add-issue-labels.lock.yml b/.github/workflows/test-claude-add-issue-labels.lock.yml index 37b00ce8c1..15eeb52669 100644 --- a/.github/workflows/test-claude-add-issue-labels.lock.yml +++ b/.github/workflows/test-claude-add-issue-labels.lock.yml @@ -881,6 +881,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-claude-command.lock.yml b/.github/workflows/test-claude-command.lock.yml index dc1f4e5fd9..f7924c7c45 100644 --- a/.github/workflows/test-claude-command.lock.yml +++ b/.github/workflows/test-claude-command.lock.yml @@ -1054,6 +1054,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-claude-create-issue.lock.yml b/.github/workflows/test-claude-create-issue.lock.yml index ee2fd92b9b..0fbdf6ab4d 100644 --- a/.github/workflows/test-claude-create-issue.lock.yml +++ b/.github/workflows/test-claude-create-issue.lock.yml @@ -554,6 +554,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-claude-create-pull-request-review-comment.lock.yml b/.github/workflows/test-claude-create-pull-request-review-comment.lock.yml index 456245de4c..49a801abf1 100644 --- a/.github/workflows/test-claude-create-pull-request-review-comment.lock.yml +++ b/.github/workflows/test-claude-create-pull-request-review-comment.lock.yml @@ -827,6 +827,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-claude-create-pull-request.lock.yml b/.github/workflows/test-claude-create-pull-request.lock.yml index bd15bc86d3..cde7307289 100644 --- a/.github/workflows/test-claude-create-pull-request.lock.yml +++ b/.github/workflows/test-claude-create-pull-request.lock.yml @@ -630,6 +630,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-claude-create-repository-security-advisory.lock.yml b/.github/workflows/test-claude-create-repository-security-advisory.lock.yml index a079d8841d..c3e6b5f568 100644 --- a/.github/workflows/test-claude-create-repository-security-advisory.lock.yml +++ b/.github/workflows/test-claude-create-repository-security-advisory.lock.yml @@ -816,6 +816,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-claude-mcp.lock.yml b/.github/workflows/test-claude-mcp.lock.yml index a0c8affbcb..79fc4531d1 100644 --- a/.github/workflows/test-claude-mcp.lock.yml +++ b/.github/workflows/test-claude-mcp.lock.yml @@ -836,6 +836,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-claude-push-to-branch.lock.yml b/.github/workflows/test-claude-push-to-branch.lock.yml index 599fa9ee90..a88e3825f7 100644 --- a/.github/workflows/test-claude-push-to-branch.lock.yml +++ b/.github/workflows/test-claude-push-to-branch.lock.yml @@ -723,6 +723,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-claude-update-issue.lock.yml b/.github/workflows/test-claude-update-issue.lock.yml index 6990730302..5191edd64b 100644 --- a/.github/workflows/test-claude-update-issue.lock.yml +++ b/.github/workflows/test-claude-update-issue.lock.yml @@ -884,6 +884,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-codex-add-issue-comment.lock.yml b/.github/workflows/test-codex-add-issue-comment.lock.yml index 3585d4366c..34deb864dc 100644 --- a/.github/workflows/test-codex-add-issue-comment.lock.yml +++ b/.github/workflows/test-codex-add-issue-comment.lock.yml @@ -712,6 +712,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-codex-add-issue-labels.lock.yml b/.github/workflows/test-codex-add-issue-labels.lock.yml index 968780724b..de5d2b3d17 100644 --- a/.github/workflows/test-codex-add-issue-labels.lock.yml +++ b/.github/workflows/test-codex-add-issue-labels.lock.yml @@ -712,6 +712,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-codex-command.lock.yml b/.github/workflows/test-codex-command.lock.yml index 3021652129..174bd80d93 100644 --- a/.github/workflows/test-codex-command.lock.yml +++ b/.github/workflows/test-codex-command.lock.yml @@ -1054,6 +1054,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-codex-create-issue.lock.yml b/.github/workflows/test-codex-create-issue.lock.yml index f4217e7342..e4753ca11a 100644 --- a/.github/workflows/test-codex-create-issue.lock.yml +++ b/.github/workflows/test-codex-create-issue.lock.yml @@ -385,6 +385,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-codex-create-pull-request-review-comment.lock.yml b/.github/workflows/test-codex-create-pull-request-review-comment.lock.yml index 15546410e5..663593f952 100644 --- a/.github/workflows/test-codex-create-pull-request-review-comment.lock.yml +++ b/.github/workflows/test-codex-create-pull-request-review-comment.lock.yml @@ -658,6 +658,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-codex-create-pull-request.lock.yml b/.github/workflows/test-codex-create-pull-request.lock.yml index 13a9864f03..23038b6b58 100644 --- a/.github/workflows/test-codex-create-pull-request.lock.yml +++ b/.github/workflows/test-codex-create-pull-request.lock.yml @@ -451,6 +451,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-codex-create-repository-security-advisory.lock.yml b/.github/workflows/test-codex-create-repository-security-advisory.lock.yml index 788788b77d..78c13da76c 100644 --- a/.github/workflows/test-codex-create-repository-security-advisory.lock.yml +++ b/.github/workflows/test-codex-create-repository-security-advisory.lock.yml @@ -647,6 +647,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-codex-mcp.lock.yml b/.github/workflows/test-codex-mcp.lock.yml index efc2512c55..91fa57b1a8 100644 --- a/.github/workflows/test-codex-mcp.lock.yml +++ b/.github/workflows/test-codex-mcp.lock.yml @@ -664,6 +664,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-codex-push-to-branch.lock.yml b/.github/workflows/test-codex-push-to-branch.lock.yml index c7bf085afc..dae4602749 100644 --- a/.github/workflows/test-codex-push-to-branch.lock.yml +++ b/.github/workflows/test-codex-push-to-branch.lock.yml @@ -582,6 +582,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-codex-update-issue.lock.yml b/.github/workflows/test-codex-update-issue.lock.yml index e8062d6007..8ebb40d869 100644 --- a/.github/workflows/test-codex-update-issue.lock.yml +++ b/.github/workflows/test-codex-update-issue.lock.yml @@ -715,6 +715,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-custom-safe-outputs.lock.yml b/.github/workflows/test-custom-safe-outputs.lock.yml index 49e3e2770e..9e40737666 100644 --- a/.github/workflows/test-custom-safe-outputs.lock.yml +++ b/.github/workflows/test-custom-safe-outputs.lock.yml @@ -567,6 +567,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/.github/workflows/test-proxy.lock.yml b/.github/workflows/test-proxy.lock.yml index 849d4d8122..1d7bec7fee 100644 --- a/.github/workflows/test-proxy.lock.yml +++ b/.github/workflows/test-proxy.lock.yml @@ -797,6 +797,15 @@ jobs: */ function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); // Fix missing quotes around object keys diff --git a/pkg/workflow/js/collect_ndjson_output.cjs b/pkg/workflow/js/collect_ndjson_output.cjs index a731863c7f..18c9e14553 100644 --- a/pkg/workflow/js/collect_ndjson_output.cjs +++ b/pkg/workflow/js/collect_ndjson_output.cjs @@ -198,6 +198,16 @@ async function main() { function repairJson(jsonStr) { let repaired = jsonStr.trim(); + // remove invalid control characters like + // U+0014 (DC4) — represented here as "\u0014" + // Escape control characters not allowed in JSON strings (U+0000 through U+001F) + // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + const _ctrl = {8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r"}; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); + // Fix single quotes to double quotes (must be done first) repaired = repaired.replace(/'/g, '"'); diff --git a/pkg/workflow/js/collect_ndjson_output.test.cjs b/pkg/workflow/js/collect_ndjson_output.test.cjs index d792436973..ef4ca8b703 100644 --- a/pkg/workflow/js/collect_ndjson_output.test.cjs +++ b/pkg/workflow/js/collect_ndjson_output.test.cjs @@ -793,6 +793,203 @@ Line 3"} expect(parsedOutput.errors).toHaveLength(0); }); + it("should repair JSON with control characters (null, backspace, form feed)", async () => { + const testFile = "/tmp/test-ndjson-output.txt"; + // Test with actual control characters: null (\x00), backspace (\x08), form feed (\x0C) + const ndjsonContent = `{"type": "create-issue", "title": "Test\x00Issue", "body": "Body\x08with\x0Ccontrol\x07chars"}`; + + fs.writeFileSync(testFile, ndjsonContent); + process.env.GITHUB_AW_SAFE_OUTPUTS = testFile; + process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG = '{"create-issue": true}'; + + await eval(`(async () => { ${collectScript} })()`); + + const setOutputCalls = mockCore.setOutput.mock.calls; + const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); + + const parsedOutput = JSON.parse(outputCall[1]); + expect(parsedOutput.items).toHaveLength(1); + expect(parsedOutput.items[0].type).toBe("create-issue"); + // Control characters should be removed by sanitizeContent after repair + expect(parsedOutput.items[0].title).toBe("TestIssue"); + expect(parsedOutput.items[0].body).toBe("Bodywithcontrolchars"); + expect(parsedOutput.errors).toHaveLength(0); + }); + + it("should repair JSON with device control characters", async () => { + const testFile = "/tmp/test-ndjson-output.txt"; + // Test with device control characters: DC1 (\x11), DC4 (\x14), NAK (\x15) + const ndjsonContent = `{"type": "create-issue", "title": "Device\x11Control\x14Test", "body": "Text\x15here"}`; + + fs.writeFileSync(testFile, ndjsonContent); + process.env.GITHUB_AW_SAFE_OUTPUTS = testFile; + process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG = '{"create-issue": true}'; + + await eval(`(async () => { ${collectScript} })()`); + + const setOutputCalls = mockCore.setOutput.mock.calls; + const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); + + const parsedOutput = JSON.parse(outputCall[1]); + expect(parsedOutput.items).toHaveLength(1); + expect(parsedOutput.items[0].type).toBe("create-issue"); + // Control characters should be removed by sanitizeContent after repair + expect(parsedOutput.items[0].title).toBe("DeviceControlTest"); + expect(parsedOutput.items[0].body).toBe("Texthere"); + expect(parsedOutput.errors).toHaveLength(0); + }); + + it("should repair JSON preserving valid escape sequences (newline, tab, carriage return)", async () => { + const testFile = "/tmp/test-ndjson-output.txt"; + // Test that valid control characters (tab, newline, carriage return) are properly handled + // Note: These should be properly escaped in the JSON to avoid breaking the JSONL format + const ndjsonContent = `{"type": "create-issue", "title": "Valid\\tTab", "body": "Line1\\nLine2\\rCarriage"}`; + + fs.writeFileSync(testFile, ndjsonContent); + process.env.GITHUB_AW_SAFE_OUTPUTS = testFile; + process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG = '{"create-issue": true}'; + + await eval(`(async () => { ${collectScript} })()`); + + const setOutputCalls = mockCore.setOutput.mock.calls; + const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); + + const parsedOutput = JSON.parse(outputCall[1]); + expect(parsedOutput.items).toHaveLength(1); + expect(parsedOutput.items[0].type).toBe("create-issue"); + // Escaped sequences in JSON should become actual characters, then get sanitized appropriately + expect(parsedOutput.items[0].title).toBe("Valid\tTab"); // Tab preserved by sanitizeContent + expect(parsedOutput.items[0].body).toBe("Line1\nLine2\rCarriage"); // Newlines/returns preserved + expect(parsedOutput.errors).toHaveLength(0); + }); + + it("should repair JSON with mixed control characters and regular escape sequences", async () => { + const testFile = "/tmp/test-ndjson-output.txt"; + // Test mixing regular escapes with control characters - simplified to avoid quote issues + const ndjsonContent = `{"type": "create-issue", "title": "Mixed\x00test\\nwith text", "body": "Body\x02with\\ttab\x03end"}`; + + fs.writeFileSync(testFile, ndjsonContent); + process.env.GITHUB_AW_SAFE_OUTPUTS = testFile; + process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG = '{"create-issue": true}'; + + await eval(`(async () => { ${collectScript} })()`); + + const setOutputCalls = mockCore.setOutput.mock.calls; + const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); + + const parsedOutput = JSON.parse(outputCall[1]); + expect(parsedOutput.items).toHaveLength(1); + expect(parsedOutput.items[0].type).toBe("create-issue"); + // Control chars removed (\x00, \x02, \x03), escaped sequences processed (\n, \t preserved) + expect(parsedOutput.items[0].title).toMatch(/Mixedtest\nwith text/); + expect(parsedOutput.items[0].body).toMatch(/Bodywith\ttabend/); + expect(parsedOutput.errors).toHaveLength(0); + }); + + it("should repair JSON with DEL character (0x7F) and other high control chars", async () => { + const testFile = "/tmp/test-ndjson-output.txt"; + // DEL (0x7F) should be handled by sanitizeContent, other control chars by repairJson + const ndjsonContent = `{"type": "create-issue", "title": "Test\x7FDel", "body": "Body\x1Fwith\x01control"}`; + + fs.writeFileSync(testFile, ndjsonContent); + process.env.GITHUB_AW_SAFE_OUTPUTS = testFile; + process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG = '{"create-issue": true}'; + + await eval(`(async () => { ${collectScript} })()`); + + const setOutputCalls = mockCore.setOutput.mock.calls; + const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); + + const parsedOutput = JSON.parse(outputCall[1]); + expect(parsedOutput.items).toHaveLength(1); + expect(parsedOutput.items[0].type).toBe("create-issue"); + // All control characters should be removed by sanitizeContent + expect(parsedOutput.items[0].title).toBe("TestDel"); + expect(parsedOutput.items[0].body).toBe("Bodywithcontrol"); + expect(parsedOutput.errors).toHaveLength(0); + }); + + it("should repair JSON with all ASCII control characters in sequence", async () => { + const testFile = "/tmp/test-ndjson-output.txt"; + // Test simpler case to verify control character handling works + const ndjsonContent = `{"type": "create-issue", "title": "Control test\x00\x01\x02\\t\\n", "body": "End of test"}`; + + fs.writeFileSync(testFile, ndjsonContent); + process.env.GITHUB_AW_SAFE_OUTPUTS = testFile; + process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG = '{"create-issue": true}'; + + await eval(`(async () => { ${collectScript} })()`); + + const setOutputCalls = mockCore.setOutput.mock.calls; + const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); + + const parsedOutput = JSON.parse(outputCall[1]); + expect(parsedOutput.items).toHaveLength(1); + expect(parsedOutput.items[0].type).toBe("create-issue"); + + // Control chars (0x00, 0x01, 0x02) removed, tab and newline preserved + const title = parsedOutput.items[0].title; + expect(title).toBe("Control test"); // Control chars actually get removed completely + expect(parsedOutput.items[0].body).toBe("End of test"); + + expect(parsedOutput.errors).toHaveLength(0); + }); + + it("should test control character repair in isolation using the repair function", async () => { + const testFile = "/tmp/test-ndjson-output.txt"; + // Test malformed JSON that needs both control char repair and other repairs + const ndjsonContent = `{type: "create-issue", title: 'Test\x00with\x08control\x0Cchars', body: 'Body\x01text',}`; + + fs.writeFileSync(testFile, ndjsonContent); + process.env.GITHUB_AW_SAFE_OUTPUTS = testFile; + process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG = '{"create-issue": true}'; + + await eval(`(async () => { ${collectScript} })()`); + + const setOutputCalls = mockCore.setOutput.mock.calls; + const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); + + const parsedOutput = JSON.parse(outputCall[1]); + expect(parsedOutput.items).toHaveLength(1); + expect(parsedOutput.items[0].type).toBe("create-issue"); + // This tests that the repair function successfully handles both JSON syntax errors + // (single quotes, missing quotes around keys, trailing comma) AND control characters + expect(parsedOutput.items[0].title).toBe("Testwithcontrolchars"); + expect(parsedOutput.items[0].body).toBe("Bodytext"); + expect(parsedOutput.errors).toHaveLength(0); + }); + + it("should test repair function behavior with specific control character scenarios", async () => { + const testFile = "/tmp/test-ndjson-output.txt"; + // Test case where control characters would break JSON but repair fixes them + const ndjsonContent = `{"type": "create-issue", "title": "Control\x00\x07\x1A", "body": "Test\x08\x1Fend"}`; + + fs.writeFileSync(testFile, ndjsonContent); + process.env.GITHUB_AW_SAFE_OUTPUTS = testFile; + process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG = '{"create-issue": true}'; + + await eval(`(async () => { ${collectScript} })()`); + + const setOutputCalls = mockCore.setOutput.mock.calls; + const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); + + const parsedOutput = JSON.parse(outputCall[1]); + expect(parsedOutput.items).toHaveLength(1); + expect(parsedOutput.items[0].type).toBe("create-issue"); + // Control characters should be removed by sanitizeContent after repair escapes them + expect(parsedOutput.items[0].title).toBe("Control"); + expect(parsedOutput.items[0].body).toBe("Testend"); + expect(parsedOutput.errors).toHaveLength(0); + }); + it("should repair JSON with numbers, booleans, and null values", async () => { const testFile = "/tmp/test-ndjson-output.txt"; const ndjsonContent = `{type: 'create-issue', title: 'Complex types test', body: 'Body text', priority: 5, urgent: true, assignee: null,}`;