{"record":{"id":"96c192231fd56444","repo":"can1357/oh-my-pi","slug":"protocol-touppercase-requires-options-command","errorCode":null,"errorMessage":"${protocol.toUpperCase()} requires options.commandBinary pointing to curl","messagePattern":"(.+?) requires options\\.commandBinary pointing to curl","errorType":"validation","errorClass":"DestinationUnavailableError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts","lineNumber":220,"sourceCode":"\t\tconst connectionName = `blob-${username}-${host}-${port}`.replace(/[^A-Za-z0-9._-]/g, \"-\");\n\t\treturn {\n\t\t\tdestination: \"ftp\",\n\t\t\tasync upload(request) {\n\t\t\t\tconst filename = safeFileName(request);\n\t\t\t\tawait writeRemoteFile(\n\t\t\t\t\t{ name: connectionName, host, username, port, ...(keyPath ? { keyPath } : {}) },\n\t\t\t\t\tremotePath(directory, filename),\n\t\t\t\t\trequest.bytes,\n\t\t\t\t\t{},\n\t\t\t\t);\n\t\t\t\treturn publication(\"ftp\", request, publicUrl(publicBase, directory, filename));\n\t\t\t},\n\t\t};\n\t}\n\n\tconst binary = optionString(config, \"commandBinary\");\n\tif (!binary) {\n\t\tthrow new DestinationUnavailableError(\n\t\t\t\"ftp\",\n\t\t\t`${protocol.toUpperCase()} requires options.commandBinary pointing to curl`,\n\t\t);\n\t}\n\tconst password = credentialString(config, \"password\") ?? \"\";\n\tconst port = optionNumber(config, \"port\", 21) ?? 21;\n\treturn {\n\t\tdestination: \"ftp\",\n\t\tasync upload(request) {\n\t\t\tconst filename = safeFileName(request);\n\t\t\tconst destinationPath = remotePath(directory, filename);\n\t\t\tconst args = [\n\t\t\t\tbinary,\n\t\t\t\t\"--fail\",\n\t\t\t\t\"--silent\",\n\t\t\t\t\"--show-error\",\n\t\t\t\t\"--ftp-create-dirs\",\n\t\t\t\t\"--upload-file\",","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts#L202-L238","documentation":"A DestinationUnavailableError thrown when the ftp/ftps destination (non-sftp path, which shells out to curl) has no options.commandBinary configured. Because the broker does not bundle an FTP client, it requires an explicit path/binary name for curl and refuses to guess one.","triggerScenarios":"Destination \"ftp\" with options.protocol \"ftp\" or \"ftps\" (or protocol unset resolves only for sftp — here protocol is ftp/ftps) and options.commandBinary missing, null, or empty string.","commonSituations":"New self-hosted FTP destinations where the curl requirement was missed; minimal containers/CI images without curl installed; users assuming a system ftp client will be used automatically.","solutions":["Set options.commandBinary to your curl executable, e.g. \"/usr/bin/curl\" or \"curl\" if on PATH.","Install curl in the environment running the upload (apt-get install curl / apk add curl).","Verify curl supports FTP (--ftp-create-dirs, --upload-file) — standard builds do; the binary is invoked with --fail --silent --show-error.","If you actually want SFTP, set options.protocol to \"sftp\" instead, which uses the built-in SSH transport and needs no curl."],"exampleFix":"// before\n{ \"destination\": \"ftp\", \"options\": { \"protocol\": \"ftps\", \"host\": \"ftp.example.com\", \"publicBaseUrl\": \"https://f.example.com\" } }\n// after\n{ \"destination\": \"ftp\", \"options\": { \"protocol\": \"ftps\", \"host\": \"ftp.example.com\", \"commandBinary\": \"/usr/bin/curl\", \"publicBaseUrl\": \"https://f.example.com\" } }","handlingStrategy":"validation","validationCode":"if (['ftp', 'ftps'].includes(dest.options?.protocol ?? 'ftp') && !dest.options?.commandBinary) {\n  throw new Error('ftp/ftps destinations require options.commandBinary pointing to curl');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const uploader = createSelfHostedUploader('ftp', config);\n} catch (err) {\n  if (err?.name === 'DestinationUnavailableError' && /requires options\\.commandBinary/.test(err.message)) {\n    // install curl or set commandBinary, then re-create the destination\n  } else throw err;\n}","preventionTips":["Verify the curl binary exists at deployment time (Bun.which('curl')) before accepting ftp destinations.","Pin an absolute curl path in config for reproducibility across environments.","Include curl in container/CI base images used for uploads.","Prefer protocol 'sftp' (built-in SSH transport) when curl is not available."],"tags":["ftp","curl","configuration","missing-dependency"],"backgroundTag":"missing-required-option","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}