In short: it picks exactly one file, loads it as an editable TOML document (or starts a new one), sets the
value, creating parent tables as needed, and writes the whole file back. It never validates the value against
jj's schema. Only when the name is user.name or user.email does it load the
workspace, to warn that the working-copy commit keeps its old author.
The command is cmd_config_set. Its help text: "Update a config
file to set the given option to a given value." (source). It is the
Set variant of ConfigCommand, with the visible alias jj config s
(source, dispatch).
1. Command-line surface
| Argument | Meaning |
|---|---|
NAME (required) | A dotted config name, parsed as a TOML key, so a component can be quoted (test-table.'bar()'). Shell completion offers the leaf keys of jj's config schema (source). |
VALUE (required) | A TOML expression. Text that does not parse is taken as a bare string, unless it looks like a TOML string, array or table that is malformed (source). |
--user | The user config file (source). |
--repo | The repo's config file (source). |
--workspace | The current workspace's config file (source). |
--file PATH | A specific file, which must be a location jj recognizes: a user, repo or workspace config, a file in a conf.d/ directory, or one loaded via system configs, $JJ_CONFIG or --config-file. It is created, with missing parents, if absent (source). |
Exactly one of the four targets is required: they form a clap group with multiple = false,
required = true (source). Unlike config get, the
command never looks at the merged config to find a value; it edits one file.
The global flags are on the CLI page.
2. Configuration
The loaded config decides which file is edited, not what is written. With --user,
--repo or --workspace, the first existing file of that level that was loaded is used;
if none was, the default path for that level is used, creating the user config's parent directory if needed
(source, user). So with two user
config files, --user edits the first (test). Any key can be
set, known to jj or not; a wrong type or value is only reported by a later command that reads it
(test).
3. End-to-end flow
4. Core architecture touchpoints
| Piece | Role here |
|---|---|
ConfigTargetArgs::edit_config_file | Shared with config edit and config unset: turns the target flag into one ConfigFile, or fails with "No user/repo/workspace config path found to edit". |
ConfigEnv::resolve_file_to_edit | Accepts a --file path only if it is a loaded layer's file, the repo or workspace config path, a user config path, or a .toml file in a user config directory. |
ConfigFile | A config layer plus the path it saves to. Its document is a toml_edit document, so comments and layout elsewhere in the file are kept. |
ensure_table | Walks the parent keys, creating each missing one as an implicit table, and reports the first that is not a table. |
CommandHelper::workspace_helper | Used only by the author check, to read the working-copy commit's author. |
5. What it reads, and snapshotting
For most names it reads only the chosen config file, and never the repo. --repo needs the repo's
config path, which may generate a config ID for the repo (source).
For user.name and user.email, the author check calls workspace_helper,
which loads the workspace and snapshots the working copy if allowed
(source). If no workspace can be loaded, the check is skipped silently so
the command still works outside a repo (source). It writes one file.
6. Algorithm and invariants
Parsing the value
parse_value_or_bare_string first parses the text as a TOML
value. If that fails, the text becomes a string unless, after trimming, it starts with ",
', [ or {, or ends with ", ', ] or
}; empty or whitespace-only text is a string too (source). So
a + b is stored as "a + b", while ['typo'} is rejected
(test).
Choosing the file
A --file path is joined to the current directory, then checked in order against the loaded
layers, the repo config path, the workspace config path and the user config paths or directories; anything else
fails with "is not a valid jj configuration file location" (source).
A missing file is loaded as an empty document beginning with
#:schema https://docs.jj-vcs.dev/latest/config-schema.json
(source).
Setting the value
ConfigLayer::set_value creates the parent tables. If the
leaf exists and is a value (an array or inline table counts), it is replaced in place; if it is a table, the
update fails. A new leaf is appended with its key formatting reset. The rest of the file, including a
--when condition and its quoting style, is left as it was (test). The whole
document is then written to a temporary file beside the target and renamed over it, so the file is
replaced atomically (source). The temporary file takes the old file's
permissions, and if the path is a symlink to an existing file, the symlink's target is replaced
instead of the link (new_temp_file_for_replacement).
The author warning
The check runs before the value is set. If the working-copy commit's author name (or email) is not exactly the
new value as a string, it warns that the setting only affects future commits and suggests
jj metaedit --update-author (source). It does not change the
working-copy commit.
7. Errors and edge cases
| Situation | Result |
|---|---|
| No target, name or value | A clap usage error listing the missing ones, exit 2 (test). |
| Empty name | A TOML parse error for <NAME>, exit 2 (test). |
| Malformed TOML-looking value | A TOML parse error for <VALUE>, exit 2 (test). |
| Name is an existing table | Error: Failed to set NAME / Caused by: Would overwrite entire table NAME, exit 1 (test). |
| A parent is not a table | Caused by: Would overwrite non-table value with parent table NAME, exit 1 (test). |
--file outside known locations | "Configuration file '…' is not a valid jj configuration file location", with a hint; a new .toml file in the user config directory is allowed (test). |
| Invalid value for a known key | Written anyway; later commands fail with Config error: Invalid type or value (test). |
8. Example output
From upstream's tests. Three sets into a new user config file produce (source):
$ jj config set --user test-key test-val
$ jj config set --user test-table.foo true
$ jj config set --user test-table.'bar()' 0
$ cat ~/.config/jj/config.toml
#:schema https://docs.jj-vcs.dev/latest/config-schema.json
test-key = "test-val"
[test-table]
foo = true
'bar()' = 0
Changing the email in a repo whose working-copy commit has another author (source):
$ jj config set --repo user.email "'Foo'"
Warning: This setting will only impact future commits.
The author of the working copy will stay "Test User <test.user@example.com>".
To change the working copy author, use "jj metaedit --update-author".