{"record":{"id":"aaac6dee20d3021d","repo":"aaif-goose/goose","slug":"invalid-deeplink-or-recipe-format","errorCode":null,"errorMessage":"Invalid deeplink or recipe format","messagePattern":"Invalid deeplink or recipe format","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ui/desktop/src/components/recipes/ImportRecipeForm.tsx","lineNumber":126,"sourceCode":"\n  const importRecipeForm = useForm({\n    defaultValues: {\n      deeplink: '',\n      recipeUploadFile: null as File | null,\n    },\n    validators: {\n      onChange: importRecipeSchema,\n    },\n    onSubmit: async ({ value }) => {\n      setImporting(true);\n      try {\n        let recipe: Recipe;\n\n        // Parse recipe from either deeplink or recipe file\n        if (value.deeplink && value.deeplink.trim()) {\n          const parsedRecipe = await parseDeeplink(value.deeplink.trim());\n          if (!parsedRecipe) {\n            throw new Error('Invalid deeplink or recipe format');\n          }\n          recipe = parsedRecipe;\n        } else {\n          const fileContent = await value.recipeUploadFile!.text();\n          recipe = await parseRecipeFromFile(fileContent);\n        }\n\n        await saveRecipe(recipe, null);\n\n        // Reset dialog state\n        importRecipeForm.reset({\n          deeplink: '',\n          recipeUploadFile: null,\n        });\n        onClose();\n\n        onSuccess();\n","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/ui/desktop/src/components/recipes/ImportRecipeForm.tsx#L108-L144","documentation":"Thrown by the recipe import dialog in the goose desktop app when parseDeeplink returns null. parseDeeplink (ui/desktop/src/recipe/index.ts:93) returns null — never throws — whenever the trimmed input does not start with 'goose://recipe?config=', has an empty payload, fails decodeRecipe, or the decoded recipe lacks title/description or both instructions and prompt. The form collapses all of those distinct failures into this single message.","triggerScenarios":"Pasting raw recipe YAML into the deeplink field; pasting a goose:// link for a non-recipe target such as goose://extension?...; a config= payload that is truncated so base64/decode fails; a decoded recipe that has title and description but neither instructions nor prompt.","commonSituations":"Confusing the deeplink field with the file import path; links copied from chat clients that ellipsize long URLs; recipes authored by hand that omit instructions/prompt; whitespace or a newline inside the pasted link breaking the prefix check or payload.","solutions":["Paste a link that starts exactly with goose://recipe?config= — generate one from the deeplink generator page or the desktop share flow","If importing a YAML file, leave the deeplink field empty and use the file picker; a non-empty deeplink always takes precedence","Re-copy the entire link (truncated config payloads fail decode)","If you generate the links yourself, ensure the encoded recipe has title, description, and instructions or prompt"],"exampleFix":"// before\nconst parsedRecipe = await parseDeeplink(value.deeplink.trim());\nif (!parsedRecipe) {\n  throw new Error('Invalid deeplink or recipe format');\n}\n\n// after\nconst link = value.deeplink.trim();\nif (!link.startsWith('goose://recipe?config=')) {\n  importRecipeForm.setError('deeplink', 'Link must start with goose://recipe?config=');\n  return;\n}\nconst parsedRecipe = await parseDeeplink(link);\nif (!parsedRecipe) {\n  importRecipeForm.setError(\n    'deeplink',\n    'Decoded recipe is invalid: it needs title, description, and instructions or prompt'\n  );\n  return;\n}","handlingStrategy":"try-catch","validationCode":"const link = value.deeplink.trim();\nif (link && !link.startsWith('goose://recipe?config=')) {\n  importRecipeForm.setError('deeplink', 'Link must start with goose://recipe?config=');\n}","typeGuard":"const isRecipe = (r: Recipe | null): r is Recipe =>\n  r !== null &&\n  Boolean(r.title) &&\n  Boolean(r.description) &&\n  (Boolean(r.instructions) || Boolean(r.prompt));","tryCatchPattern":"try {\n  const recipe = await parseDeeplink(link);\n  if (!isRecipe(recipe)) {\n    importRecipeForm.setError('deeplink', 'Recipe needs title, description, and instructions or prompt');\n    return;\n  }\n  await saveRecipe(recipe, null);\n} catch (e) {\n  importRecipeForm.setError('deeplink', e instanceof Error ? e.message : 'Import failed');\n} finally {\n  setImporting(false);\n}","preventionTips":["Generate links from the deeplink generator page or the desktop share flow instead of hand-building them","Paste complete links; truncated config= payloads always fail decode","Use the file picker for YAML imports and keep the deeplink field empty"],"tags":["deeplink","recipe","parsing","desktop","import"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}