jj / How jj config set works

How jj config set works

jj config set --user|--repo|--workspace|--file PATH NAME VALUE writes one value into one config file, keeping the rest of the file's formatting.

On this page
  1. 1. Command-line surface
  2. 2. Configuration
  3. 3. End-to-end flow
  4. 4. Core architecture touchpoints
  5. 5. What it reads, and snapshotting
  6. 6. Algorithm and invariants
  7. Parsing the value
  8. Choosing the file
  9. Setting the value
  10. The author warning
  11. 7. Errors and edge cases
  12. 8. Example output

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

ArgumentMeaning
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).
--userThe user config file (source).
--repoThe repo's config file (source).
--workspaceThe current workspace's config file (source).
--file PATHA 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

jj config set: call flow clap parses the name and the value, turning unparsable text into a bare string. edit_config_file picks one file: the first loaded file of the chosen level, the level's default path, or a --file path checked by resolve_file_to_edit. The file is loaded as an editable TOML document, or started with a schema comment if missing. For user.name and user.email, the workspace is loaded to warn if the working-copy author differs. set_value creates parent tables and replaces or inserts the value, failing if that would overwrite a table or put a table under a non-table value. Finally the whole file is written back. clap: NAME, VALUE, one target unparsable VALUE → bare string edit_config_file: pick one file --file → resolve_file_to_edit ConfigFile::load_or_empty missing → new doc with #:schema line user.name / user.email only check_wc_author → warning ConfigLayer::set_value ensure_table parents; replace or insert would overwrite a table Error: Failed to set NAME, exit 1 ConfigFile::save temp file renamed over the file

4. Core architecture touchpoints

PieceRole here
ConfigTargetArgs::edit_config_fileShared 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_editAccepts 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.
ConfigFileA 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_tableWalks the parent keys, creating each missing one as an implicit table, and reports the first that is not a table.
CommandHelper::workspace_helperUsed 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

SituationResult
No target, name or valueA clap usage error listing the missing ones, exit 2 (test).
Empty nameA TOML parse error for <NAME>, exit 2 (test).
Malformed TOML-looking valueA TOML parse error for <VALUE>, exit 2 (test).
Name is an existing tableError: Failed to set NAME / Caused by: Would overwrite entire table NAME, exit 1 (test).
A parent is not a tableCaused 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 keyWritten 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".