aaif-goose/goose · error · anyhow::Error
--allowed-origin must be a non-wildcard Origin value
Error message
--allowed-origin must be a non-wildcard Origin value
What it means
anyhow bail from 'goose serve' origin validation (crates/goose-cli/src/cli.rs). Each --allowed-origin value is trimmed and rejected if it is empty or exactly '*': CORS origins must be concrete values, and wildcard CORS is not offered via this flag (auth via secret key remains mandatory instead).
Source
Thrown at crates/goose-cli/src/cli.rs:1466
.map(|secret| secret.trim().to_string())
.filter(|secret| !secret.is_empty());
let require_token = env_secret.is_some();
if !require_token && !dangerously_unauthenticated {
anyhow::bail!(
"{GOOSE_SERVER_SECRET_KEY_ENV} must be set to start `goose serve`; pass --dangerously-unauthenticated to run without ACP authentication"
);
}
if dangerously_unauthenticated && !require_token {
warn!(
"{GOOSE_SERVER_SECRET_KEY_ENV} is not set and --dangerously-unauthenticated was passed; the ACP endpoint will accept unauthenticated connections"
);
}
let additional_allowed_origins = allowed_origins
.into_iter()
.map(|origin| {
let origin = origin.trim();
if origin.is_empty() || origin == "*" {
anyhow::bail!("--allowed-origin must be a non-wildcard Origin value");
}
HeaderValue::from_str(origin).map_err(|error| {
anyhow::anyhow!("invalid --allowed-origin value `{origin}`: {error}")
})
})
.collect::<Result<Vec<_>>>()?;
let secret_key = env_secret.unwrap_or_else(generate_serve_secret_key);
if let Err(error) = server.start_scheduler().await {
warn!("Scheduler failed to start; scheduled jobs will not run until a client connects: {error}");
}
let router = create_router(
server,
secret_key,
require_token,
additional_allowed_origins,
);
let config = Config::global();View on GitHub (pinned to 3810898a74)
Solutions
- Pass concrete origins: --allowed-origin http://localhost:1420 --allowed-origin https://app.example.com
- If a script builds flags from variables, skip the flag when the variable is empty
- Do not rely on '*' — connect clients are authenticated with the GOOSE_SERVER__SECRET_KEY token instead
- List each origin you actually serve from (dev server port + production domain)
Example fix
# before goose serve --allowed-origin '*' # after goose serve --allowed-origin http://localhost:1420 --allowed-origin https://app.example.com
Defensive patterns
Strategy: validation
Validate before calling
origin = origin.strip()
if not origin or origin == "*":
raise SystemExit("--allowed-origin must be a concrete origin like https://app.example.com") Type guard
def is_concrete_origin(v: str) -> bool:
v = v.strip()
return bool(v) and v != "*" and not v.endswith('/') Prevention
- Never pass '*' — goose authorizes clients by token, not CORS wildcard
- List each concrete origin explicitly
- Skip empty flags when building the command from variables in scripts
When it happens
Trigger: Running 'goose serve --allowed-origin "*"' or passing an empty/whitespace-only value (e.g. an env expansion that produced an empty string): origin.is_empty() || origin == "*" triggers the bail before HeaderValue parsing.
Common situations: Copy-pasting a permissive CORS setup from another tool; shell loops building --allowed-origin flags from a variable that is sometimes empty; misunderstanding that goose gates cross-origin access via the token, not via CORS wildcard.
Related errors
- invalid --allowed-origin value `{origin}`: {error}
- Cannot use --session-id without --resume
- GOOSE_SERVER__SECRET_KEY must be set to start `goose serve`;
- No session found to resume
- No session found with name '{}'
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/552ac233cc64278e.
Report an issue: GitHub.