-
Notifications
You must be signed in to change notification settings - Fork 12.6k
Description
Description
Quick summary
It seems like the config hierarchy has some problems, specifically that the defaults coded into typescript can override the user configs when a project settings file is present.
- This does not follow the config hierarchy as documented here https://opencode.ai/docs/config/
From what I was able to find, this does not affect any config setting that opencode currently has.
- It seems like no setting (other than keybinds) in opencode has a default that isn't just an empty string or whatever the default value of a type is (maybe that was a deliberate workaround for this?)
- I was not able to reproduce this with a keybind
So that means that the config system has a limitation, but as far as I know, it's not something a user would notice.
About my use case:
- This limitation is unfortunate because there's a setting I'd like to add that would have a default value that's not an empty string
- I would like to allow users to set it to an empty string, but I don't want that to be the default
What I've noticed from debugging
In state(), the way user settings are retrieved via global() seems to have special behavior
| result = mergeConfigConcatArrays(result, await global()) |
If I trace this problem,
- It seems like the default values are not present on what's returned from global()
- Config files loaded after the initial user config (e.g. project level config files) will load default values
- That means that if a config field with default values is not present in a project level config, but is present in a user config, it will take the default value rather than the user customized setting
Unfortunately a fix for this behavior might be considered a breaking change.
Possible workarounds:
A. Set a default value, and just have users put the field in their project level config as well as their user config (not ideal).
B. Don't use default("The default string value I want") and interpret whatever zod gives me for an unset field as a signal to use the default string
- Fields without defaults will have a value of
undefined - I can treat a value of
undefinedas a signal to use the default value, though this is not super ideal from a coding perspective. A typo would look the same as intended behavior.
Plugins
None
OpenCode version
v1.2.24
Steps to reproduce
1. Add a new config field with a default
At the moment the way to reproduce this is to add a new config field,
For example, add a new field with a default value to Info:
opencode/packages/opencode/src/config/config.ts
Lines 985 to 990 in c6262f9
| export const Info = z | |
| .object({ | |
| $schema: z.string().optional().describe("JSON schema reference for configuration validation"), | |
| logLevel: Log.Level.optional().describe("Log level"), | |
| server: Server.optional().describe("Server configuration for opencode serve and web commands"), | |
| command: z |
Like this:
export const Info = z
.object({
$schema: z.string().optional().describe("JSON schema reference for configuration validation"),
testField: z
.string()
.optional()
.describe("A field for testing config hierarchies")
.default("defaultValue"),2. Add some code that uses this setting
Feel free to use any method that you want to access this setting, there's probably an easier way to do this.
For the sake of this test, I hacked it into this function:
opencode/packages/opencode/src/cli/cmd/run.ts
Lines 381 to 394 in c6262f9
| async function session(sdk: OpencodeClient) { | |
| const baseID = args.continue ? (await sdk.session.list()).data?.find((s) => !s.parentID)?.id : args.session | |
| if (baseID && args.fork) { | |
| const forked = await sdk.session.fork({ sessionID: baseID }) | |
| return forked.data?.id | |
| } | |
| if (baseID) return baseID | |
| const name = title() | |
| const result = await sdk.session.create({ title: name, permission: rules }) | |
| return result.data?.id | |
| } |
I edited this function to look like this:
async function session(sdk: OpencodeClient) {
const baseID = args.continue ? (await sdk.session.list()).data?.find((s) => !s.parentID)?.id : args.session
if (baseID && args.fork) {
const forked = await sdk.session.fork({ sessionID: baseID })
return forked.data?.id
}
if (baseID) return baseID
//const name = title()
// HACK: to test custom config value
let configStr;
const config = await sdk.config.get()
if(config) {
configStr = "config.data is undefined"
if(config.data) {
configStr = "config.data.testField is undefined"
if(config.data.testField) {
configStr = config.data.testField;
}
}
}
const name = `The value of testField is ${configStr}`
const result = await sdk.session.create({ title: name, permission: rules })
return result.data?.id
}Usage
With the edits in this step, whenever you do this
opencode run "what is 2 + 2"It will put The value of testField is ... in the name of the session created by opencode run. Note that this will NOT work from the TUI.
3. Demo: The default value is working
- Build opencode by cd'ing into packages/opencode and doing
bun run build - create an empty directory somewhere on your computer
- Run
/path/to/your/built/opencode run "what is 2 + 2" - Run
opencode session list
You should see a new session named "The value of testField is defaultValue"
4. Demo: Users can override this default value in their user config for directories that do not contain a .opencode directory
- Edit your user config to set this value
E.g. in MacOS, put this in ~/.config/opencode/opencode.jsonc
- In the empty directory, run
/path/to/your/built/opencode run "what is 2 + 2"again - Run
opencode session list
You should see a new session named "The value of testField is userConfig"
5. The bug: If you run opencode in a directory containing .opencode/opencode.jsonc, it will apply default values rather than the user configuration
- In the empty directory, create a directory named .opencode and put this opencode.jsonc in it
{
"$schema": "https://opencode.ai/config.json"
}Note that it does NOT change the value of testField.
- In the directory containing the .opencode directory, run
/path/to/your/built/opencode run "what is 2 + 2"again - Run
opencode session list
You should see a new session named "The value of testField is defaultValue"
- I would have expected it to say "userConfig".
Note that you can override this setting at the project level too, and you can "fix" this by copying your config from your ~/.config/opencode directory into the project, but you'd have to do that for each directory you're in.
Screenshot and/or share link
No response
Operating System
No response
Terminal
No response
{ "$schema": "https://opencode.ai/config.json", "testField": "userConfig" }