feat: add JSON Schema for config.yaml with typed config accessors#8630
feat: add JSON Schema for config.yaml with typed config accessors#8630wpfleger96 wants to merge 1 commit into
Conversation
e7aff91 to
ba853db
Compare
ba853db to
5a4bb00
Compare
5a4bb00 to
f064808
Compare
Goose has no machine-readable schema for config.yaml and config keys were accessed via untyped string literals across the codebase. This adds a JSON Schema for IDE autocomplete and validation, with GooseConfigSchema as the authoritative registry for user-facing config keys. Typed accessors replace raw string-key reads across user-facing config call sites while compile-time assertions keep accessor declarations tied to schema membership. Raw get_param and set_param remain available for dynamic and internal config storage. Signed-off-by: Will Pfleger <wpfleger@block.xyz>
f064808 to
e44a0cd
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e44a0cdc1a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| #[serde(rename = "GOOSE_DISABLE_SESSION_NAMING")] | ||
| pub goose_disable_session_naming: Option<bool>, | ||
| #[serde(rename = "GOOSE_DISABLE_KEYRING")] | ||
| pub goose_disable_keyring: Option<String>, |
There was a problem hiding this comment.
Accept boolean keyring flag in config schema
The new schema constrains GOOSE_DISABLE_KEYRING to string|null, but runtime config parsing still explicitly supports booleans (true/false) for this key via keyring_disabled_value in config/base.rs. As a result, valid existing configs like GOOSE_DISABLE_KEYRING: true are now flagged as schema-invalid in editors/validators even though Goose honors them at runtime, which creates a misleading contract for users and tooling.
Useful? React with 👍 / 👎.
|
sorry for letting this linger so long. I do think we need something to structure this, but I wonder if this is the way. In the ideal world I think we should have something that makes it all truly type safe. I had an experiment |
|
Thanks for the PR! We have been thinking about this problem too. Before you invest more time rebasing, take a look at the The key difference is that it defines a single Rust struct that flows through the OpenAPI spec into generated TypeScript types, so the UI gets real type safety too — not just a standalone JSON Schema file. Your approach gives compile-time key-name checking on the Rust side, but the schema it produces is not consumed by anything programmatically. Would be worth discussing whether we can combine the best of both approaches before moving forward. |
|
@DOsinga I checked out the my motivation here is a bit different though - I have a side project CLI tool that manages my local agent configs via symlinks, and it performs config validation for Claude Code/Codex/Gemini against their respective published standalone JSON schemas that are fetched at test time. I'd love for goose to have its own published config schema that I could use for automated validation too I'm wondering if there's a clean "best of both" path: one typed config struct that derives both is |
|
so I worked on structured settings and then got distracted and it was out of sync so I closed the PR. I think we can do both of both worlds, if we do structured settings, we should be able to generate the schema you have, also, no? or is that what you are saying. if you would want to take over structured settings, I'd be very happy! |
|
yes exactly that's what I was trying to say! I can definitely take over structured settings |
|
closing in favor of #9197 |
Summary
crates/goose/config.schema.json) forconfig.yaml, enabling IDE autocomplete and programmatic validationGooseConfigSchemathe authoritative registry of122user-facing config fields with compile-time enforcementget_param("STRING_KEY")call sites across Goose to typed accessors (e.g.config.get_openai_host(),config.get_goose_max_turns(), etc.), including current-main keys likeOPENAI_BASE_URLMotivation
Goose did not have a machine-readable schema for
config.yaml, while other AI coding CLIs such as Codex and Claude Code support schema-backed configuration validation. Beyond the schema gap, config keys were accessed via untyped string literals scattered across the codebase -- adding a new key required no schema update, and drift between the schema and runtime access was undetectable.How it works
Schema and typed accessors work together to prevent config key drift:
config_value!macro now includesconst assert!(GooseConfigSchema::has_key(stringify!($key))). Adding aconfig_value!for a key not registered in the schema struct is a compile error.config_value!invocations generateget_*/set_*methods onConfig. Callers useconfig.get_openai_host()instead ofconfig.get_param("OPENAI_HOST"). Same return type (Result<T, ConfigError>), same IO path, just type-safe at the call site.Key taxonomy (what gets typed vs what stays dynamic):
config_value!typed accessorGOOSE_MODEL,OPENAI_HOST,GOOSE_CLI_THEMEget_paramextensions,gateway_configs,experimentsget_paramwhere key names come from runtime dataTELEMETRY_ENABLED_KEY,GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEYWhat does NOT change:
get_paramandset_paramremain public (needed by Category B/C callers)File changes
crates/goose/src/config/schema.rsNew file. Defines
GooseConfigSchemafor JSON Schema generation. AddsALL_KEYSandhas_key()for compile-time validation, plus tests for struct-to-key consistency.crates/goose/src/config/base.rsAdds
const assert!to both forms of theconfig_value!macro. Adds typed config accessors covering user-facing config keys across providers, core settings, CLI, security, observability, and tunnel settings.crates/goose/src/bin/generate_config_schema.rsNew binary. Generates
config.schema.jsonfromGooseConfigSchemaviaschemars::schema_for!.crates/goose/config.schema.jsonGenerated schema output for
config.yaml, including122properties and typed extension config definitions.crates/goose/src/config/goose_mode.rs,crates/goose/src/slash_commands.rs,crates/goose/src/agents/extension.rs,crates/goose/src/config/extensions.rsAdded
#[derive(JsonSchema)]to existing types so the schema references real types rather than duplicating definitions.crates/goose/src/providers/*.rsMigrates provider config access from raw string-key reads to typed accessors while preserving current-main behavior, including
OPENAI_BASE_URL.crates/goose/src/agents/*.rs,crates/goose/src/context_mgmt/mod.rs,crates/goose/src/security/*.rs,crates/goose/src/posthog.rs,crates/goose/src/hints/load_hints.rs,crates/goose/src/model.rs,crates/goose/src/otel/otlp.rsSame typed-accessor migration for core Goose settings, security, observability, and telemetry keys.
crates/goose-cli/src/session/*.rs,crates/goose-cli/src/commands/configure.rs,crates/goose-cli/src/recipes/*.rsMigrated CLI config access. Deleted Category D constants (
TELEMETRY_ENABLED_KEY,GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY).crates/goose-server/src/tunnel/mod.rsMigrated
tunnel_auto_startto a typed accessor.JustfileAdded
generate-config-schemaandcheck-config-schemarecipes. Added config schema check tocheck-everything..github/workflows/ci.ymlAdded a config schema freshness check to the existing schema-check path.
Reproduction steps
just check-config-schema-- should pass when the schema is up to datecargo test -p goose --lib all_keys_matches-- validatesALL_KEYSmatches struct fieldsconfig_value!(FAKE_KEY, String)tobase.rswithout adding it toGooseConfigSchema-- should fail to compileconfig.yamlin VS Code with the YAML extension and point$schemaat the raw GitHub URL ofconfig.schema.json-- should get autocomplete for config keys