{"record":{"id":"ba891e1648c4d79b","repo":"gchq/CyberChef","slug":"invalid-recipe","errorCode":null,"errorMessage":"Invalid recipe","messagePattern":"Invalid recipe","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/core/Utils.mjs","lineNumber":1056,"sourceCode":"            recipeConfig.push(op);\n        }\n        return recipeConfig;\n    }\n\n\n    /**\n     * Performs a linear structural validation pass over pretty recipe syntax.\n     *\n     * @param {string} recipe\n     * @throws {Error} if the recipe is structurally invalid\n     */\n    static _validatePrettyRecipe(recipe) {\n        let i = 0;\n\n        while (i < recipe.length) {\n            const openParen = recipe.indexOf(\"(\", i);\n            if (openParen === -1 || openParen === i) {\n                throw new Error(\"Invalid recipe\");\n            }\n\n            i = openParen + 1;\n            let inString = false,\n                escaped = false,\n                foundCloseParen = false;\n\n            for (; i < recipe.length; i++) {\n                const c = recipe[i];\n\n                if (inString) {\n                    if (escaped) {\n                        escaped = false;\n                    } else if (c === \"\\\\\") {\n                        escaped = true;\n                    } else if (c === \"'\") {\n                        inString = false;\n                    }","sourceCodeStart":1038,"sourceCodeEnd":1074,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/Utils.mjs#L1038-L1074","documentation":"Thrown by Utils._validatePrettyRecipe() (line 1056), the structural pre-check for the bespoke 'pretty' recipe format (Op_name(args)/disabled). Fires when indexOf('(') returns -1 (no opening paren anywhere from position i) OR when the next '(' sits exactly at position i (no operation name before the paren). Reached only for non-JSON recipes (strings not starting with '[').","triggerScenarios":"A pretty recipe string with no parentheses at all (e.g. 'Some Operation' with no args/parens); trailing text after a valid op that contains no '(' (the while loop's next iteration finds none); a recipe beginning with '(' so the op name before it is empty.","commonSituations":"Pasting free text or an operation name without its argument list; a multiline pretty recipe where a line was stripped of its parentheses; concatenating recipe fragments incorrectly so a trailing fragment lacks parens.","solutions":["Ensure every operation in the pretty recipe is written as Op_name(args), even if args are empty: 'From Base64()'.","Remove trailing free text after the last operation, or wrap it as an operation.","If you have JSON, pass it through as JSON (it starts with '[' and skips this validator)."],"exampleFix":"// before\nUtils.parseRecipeConfig('From Base64'); // no parens\n\n// after\nUtils.parseRecipeConfig('From Base64()');\n// or pass JSON\nUtils.parseRecipeConfig('[{\"op\":\"From Base64\",\"args\":[]}]');","handlingStrategy":"validation","validationCode":"function looksLikePrettyRecipe(s) {\n  const trimmed = s.trim();\n  if (trimmed.startsWith('[')) return false; // JSON\n  return /\\(/.test(trimmed) && /^\\S/.test(trimmed); // has a paren and a name before it\n}","typeGuard":"null","tryCatchPattern":"try { Utils.parseRecipeConfig(recipeStr); } catch (e) {\n  if (/Invalid recipe/.test(e.message) && !recipeStr.includes('(')) { /* missing parens */ }\n}","preventionTips":["Write every pretty-recipe operation as Op_name(), even with empty args.","Strip trailing free text after the last operation.","Prefer the JSON recipe form (starts with '[') for programmatic use."],"tags":["recipe","parsing","pretty-recipe","syntax","error"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}