{"record":{"id":"772f3496dfe2d939","repo":"basecamp/kamal","slug":"missing-project-or-config-from-from-project-con","errorCode":null,"errorMessage":"Missing project or config from '--from=project/config' option","messagePattern":"Missing project or config from '--from=project/config' option","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"lib/kamal/secrets/adapters/doppler.rb","lineNumber":38,"sourceCode":"      secrets = prefixed_secrets(secrets, from: from)\n      flags = secrets_get_flags(secrets)\n\n      secret_names = secrets.collect { |s| s.split(\"/\").last }\n\n      items = `doppler secrets get #{secret_names.map(&:shellescape).join(\" \")} --json #{flags}`\n      raise RuntimeError, \"Could not read #{secrets} from Doppler\" unless $?.success?\n\n      items = JSON.parse(items)\n\n      items.transform_values { |value| value[\"computed\"] }\n    end\n\n    def secrets_get_flags(secrets)\n      unless service_token_set?\n        project, config, _ = secrets.first.split(\"/\")\n\n        unless project && config\n          raise RuntimeError, \"Missing project or config from '--from=project/config' option\"\n        end\n\n        project_and_config_flags = \"-p #{project.shellescape} -c #{config.shellescape}\"\n      end\n    end\n\n    def service_token_set?\n      ENV[\"DOPPLER_TOKEN\"] && ENV[\"DOPPLER_TOKEN\"][0, 5] == \"dp.st\"\n    end\n\n    def check_dependencies!\n      raise RuntimeError, \"Doppler CLI is not installed\" unless cli_installed?\n    end\n\n    def cli_installed?\n      `doppler --version 2> /dev/null`\n      $?.success?\n    end","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/secrets/adapters/doppler.rb#L20-L56","documentation":"Raised in Doppler#secrets_get_flags when no service token is set (ENV['DOPPLER_TOKEN'] absent or not dp.st-prefixed) and the first requested secret does not decompose into project/config via split('/'). Without a service token, the doppler CLI needs -p and -c flags, which kamal derives from the `--from=project/config` option (Base#prefixed_secrets joins from + name). If either component is nil, the options cannot be built and this RuntimeError fires before any CLI call.","triggerScenarios":"adapter.fetch(secrets, from: nil) or from: 'project-only' with no dp.st.* token in ENV: secrets.first.split(\"/\") yields only one part, so project or config is nil; also triggered when from: contains extra segments in the wrong order. Note only the FIRST secret is inspected, so a valid first secret can mask bad later ones (they then fail at fetch_secrets instead).","commonSituations":"Forgetting `--from=myproject/myconfig` when calling `kamal secrets pull`; setting DOPPLER_TOKEN to a personal dp.pt token (service_token_set? false) and assuming --from is optional; passing from: 'prod' instead of 'blog/prod' in deploy.yml.","solutions":["Pass a complete from option with both slugs: `kamal secrets pull --from=myproject/myconfig` (or set the equivalent in your deploy configuration).","Or export a Doppler service token so project/config flags are unnecessary: export DOPPLER_TOKEN=dp.st.xxxx.","Verify the slugs: project and config come from the Doppler dashboard URLs (e.g. /projects/<project>/config/<config>)."],"exampleFix":"# before\nkamal secrets pull --from=prod            # only one segment\n\n# after\nkamal secrets pull --from=myblog/prod     # project/config\n# or: export DOPPLER_TOKEN=\"dp.st.1....\" and omit --from","handlingStrategy":"validation","validationCode":"def valid_doppler_from?(from)\n  return true if ENV[\"DOPPLER_TOKEN\"]&.start_with?(\"dp.st.\") # service token: --from not required\n  from.to_s.split(\"/\").length == 2 # \"project/config\"\nend\n\nabort \"Pass --from=project/config or set a dp.st.* DOPPLER_TOKEN\" unless valid_doppler_from?(ARGV[0])","typeGuard":null,"tryCatchPattern":"begin\n  adapter.fetch(names, from: from_option)\nrescue RuntimeError => e\n  retry_with_from = e.message.include?(\"Missing project or config\")\n  raise e unless retry_with_from && from_option.nil?\n  adapter.fetch(names, from: \"myproject/prod\") # explicit correct from\nend","preventionTips":["Always pass --from with two slash-separated slugs when using the Doppler adapter interactively.","Assert at script start: either ENV['DOPPLER_TOKEN'] starts with dp.st. or from is 'x/y'.","Remember only the FIRST secret is validated — keep one consistent project/config prefix for the whole list."],"tags":["ruby","kamal","doppler","secrets","missing-option","argument-validation"],"backgroundTag":"missing-required-option","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}