jj / How jj config path works

How jj config path works

jj config path prints where the user, repo or workspace config file is, whether or not the file exists. For a repo or workspace that has no config yet, it sets up a config directory first.

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. User paths
  8. Repo and workspace paths
  9. 7. Errors and edge cases
  10. 8. Example output

In short: --user prints every user config path jj considers, one per line, and creates nothing. --repo and --workspace print one path, inside the user config directory under jj/repos/<id>/ or jj/workspaces/<id>/. If that repo or workspace has no config ID yet, jj makes one: it creates the directory, writes metadata.binpb there, and writes the ID into .jj. The config.toml itself is not created.

The command is cmd_config_path. Its help text: "Print the paths to the config files", noting that a file at that path may not exist and that a config directory is generated for --repo or --workspace if needed (source). It is the Path variant of ConfigCommand, with the visible alias jj config p (source, dispatch). To open the file in an editor instead, use jj config edit.

1. Command-line surface

FlagMeaning
--userThe user-level config paths (source).
--repoThe repo-level config file (source).
--workspaceThe workspace-level config file (source).

Exactly one of the three is required: they form the clap group config_level with multiple = false, required = true (source). -R picks which repo and workspace --repo and --workspace refer to.

The global flags are on the CLI page.

2. Configuration

No config keys. The user paths come from the environment: if JJ_CONFIG is set, it is split like PATH and its non-empty entries are the user paths; otherwise jj uses ~/.jjconfig.toml (only if it exists, or if there is no platform config directory), then <config dir>/jj/config.toml, then <config dir>/jj/conf.d if that directory exists (source). The repo and workspace paths live under <config dir>/jj (source, directory names). Output is the path's bytes and a newline, with no template.

3. End-to-end flow

jj config path: call flow The command asks ConfigLevelArgs for the paths. With --user it returns every user config path, or fails if there are none. With --repo or --workspace it calls repo_config_path or workspace_config_path, which load the secure config with force set. That reads the config ID from .jj; if there is none, it generates an ID, creates the config directory with metadata.binpb, and writes the ID file. If there is no repo or workspace, it fails. Each path is then written to stdout as bytes followed by a newline. ConfigLevelArgs::config_paths exactly one of --user / --repo / --workspace --user: user_config_paths() JJ_CONFIG, or the default candidates --repo / --workspace: *_config_path load_secure_config, force = true read .jj config ID → config dir or migrate a legacy config file none yet: generate ID mkdir, metadata.binpb, write ID path_to_bytes → stdout, newline once per path

4. Core architecture touchpoints

PieceRole here
CommandHelper::config_envThe ConfigEnv built at startup. It already knows the user paths and, if a workspace was found, the repo and workspace directories (repo, workspace).
SecureConfigFinds a repo's or workspace's config file. The ID lives in .jj/repo/config-id or .jj/workspace-config-id; the file itself lives in the user config directory (source). Keeping it out of the repo means a cloned or downloaded repo cannot carry config that jj would load.
ConfigMetadataStored as metadata.binpb next to the config file; it records which .jj or .jj/repo directory the config belongs to.

5. What it reads, and snapshotting

It never snapshots and never loads the repo. --user only computes paths; upstream's test checks that no file is created (test). --repo and --workspace read the config ID file in .jj and the config directory's metadata.binpb. When no ID exists they write: the new directory, its metadata.binpb, and the ID file in .jj, written atomically through a temporary file (source, atomic write). Upstream's test checks that the legacy in-repo path .jj/repo/config.toml is not created (test).

6. Algorithm and invariants

User paths

--user prints all of user_config_paths(), in order, and fails only if the list is empty (source). A path may be a file or a conf.d-style directory (test).

Repo and workspace paths

At startup jj already ran maybe_load_config, which does not create anything (source). The command calls the forcing variant, load_config: if the earlier lookup found a config, the cached path is returned; if not, it generates a random 10-byte hex ID (source) and sets up the directory. The lookup itself (source):

  • An ID file that is not 20 hex characters is an error.
  • An ID whose directory has no metadata.binpb gets a fresh directory and a warning, "Per-repo config not found. Generating an empty one." (source).
  • If the metadata names a different directory, jj checks it (source). If that directory is gone, it assumes the repo moved and updates the metadata. If it still exists and is a separate copy, jj gives the copy its own ID and copies the config file, with a warning.
  • With no ID file, a legacy .jj/repo/config.toml or .jj/workspace-config.toml is migrated into a new config directory, with a warning (source).

7. Errors and edge cases

SituationResult
--repo outside any repoError: No repo config path found, exit 1 (source, test).
--workspace outside any workspaceError: No workspace config path found (source, test).
No user config path (for example JJ_CONFIG has only empty entries)Error: No user config path found (source).
No level flag, or more than oneA clap usage error from the config_level group (source).
Malformed config ID file"Found an invalid config ID" (source).
Path not valid as bytes on this platformA user error from path_to_bytes (source).

8. Example output

From upstream's tests, in a repo at $TEST_ENV/repo with JJ_CONFIG pointing at $TEST_ENV/config.toml. The IDs are random; the tests fix the seed (source, source):

repo$ jj config path --user
$TEST_ENV/config.toml
repo$ jj config path --repo
$TEST_ENV/home/.config/jj/repos/ffdaa62087a280bddc5e/config.toml
repo$ jj config path --workspace
$TEST_ENV/home/.config/jj/workspaces/d043564ef93650b06a70/config.toml
$ jj config path --repo
Error: No repo config path found

# with JJ_CONFIG listing a file and a directory
repo$ jj config path --user
$TEST_ENV/config/config.toml
$TEST_ENV/config/conf.d