basecamp/kamal · error · RuntimeError
Missing project or config from '--from=project/config' optio
Error message
Missing project or config from '--from=project/config' option
What it means
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.
Source
Thrown at lib/kamal/secrets/adapters/doppler.rb:38
secrets = prefixed_secrets(secrets, from: from)
flags = secrets_get_flags(secrets)
secret_names = secrets.collect { |s| s.split("/").last }
items = `doppler secrets get #{secret_names.map(&:shellescape).join(" ")} --json #{flags}`
raise RuntimeError, "Could not read #{secrets} from Doppler" unless $?.success?
items = JSON.parse(items)
items.transform_values { |value| value["computed"] }
end
def secrets_get_flags(secrets)
unless service_token_set?
project, config, _ = secrets.first.split("/")
unless project && config
raise RuntimeError, "Missing project or config from '--from=project/config' option"
end
project_and_config_flags = "-p #{project.shellescape} -c #{config.shellescape}"
end
end
def service_token_set?
ENV["DOPPLER_TOKEN"] && ENV["DOPPLER_TOKEN"][0, 5] == "dp.st"
end
def check_dependencies!
raise RuntimeError, "Doppler CLI is not installed" unless cli_installed?
end
def cli_installed?
`doppler --version 2> /dev/null`
$?.success?
endView on GitHub (pinned to eee0083b38)
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>).
Example fix
# before kamal secrets pull --from=prod # only one segment # after kamal secrets pull --from=myblog/prod # project/config # or: export DOPPLER_TOKEN="dp.st.1...." and omit --from
Defensive patterns
Strategy: validation
Validate before calling
def valid_doppler_from?(from)
return true if ENV["DOPPLER_TOKEN"]&.start_with?("dp.st.") # service token: --from not required
from.to_s.split("/").length == 2 # "project/config"
end
abort "Pass --from=project/config or set a dp.st.* DOPPLER_TOKEN" unless valid_doppler_from?(ARGV[0]) Try / catch
begin
adapter.fetch(names, from: from_option)
rescue RuntimeError => e
retry_with_from = e.message.include?("Missing project or config")
raise e unless retry_with_from && from_option.nil?
adapter.fetch(names, from: "myproject/prod") # explicit correct from
end Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Failed to login to Doppler
- Could not read #{secrets} from Doppler
- Doppler CLI is not installed
- No secrets given to fetch
- Could not authenticate to Bitwarden Secrets Manager. Did you
AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21).
Data as JSON: /api/errors/772f3496dfe2d939.
Report an issue: GitHub.