JoyJoy Docs

What joy reads from your machine

The ssh config keys, known_hosts files, proxy sources, certificate stores, credential helpers and forge CLIs joy reads, per operating system.

Not in a joy release yet

This list is the reading joy does once the release with the new forge connection is out. The newest release is v0.20.0 and it does none of it yet: it still leaves ssh configuration, known hosts, proxies and credential helpers to the git binary it spawns and to libgit2's own defaults. The page is written before that release rather than after it, so that a setup can be checked against it from the first day.

joy carries its own Git engine (libgit2 through the git2 crate) and runs no git process. That is what makes joy work on a machine with no Git installed, and it has one consequence worth writing down: everything the git binary used to read on joy's behalf, joy now has to read itself.

This page is that list. It exists so that you, or whoever supports you, can check a setup without guessing.

Your ssh config

libgit2 reads none of your ssh configuration. It takes the host and the port out of the remote URL, opens the socket itself and hands the connection to libssh2. joy therefore parses ~/.ssh/config and, after it, the machine's file: /etc/ssh/ssh_config, or %PROGRAMDATA%\ssh\ssh_config on Windows. Include lines are followed, and a Match host block is read as the host patterns it names.

The keys joy honours:

KeyWhat joy does with it
HostNameContacts this name instead of the alias you wrote in the remote
PortContacts this port
UserUses this ssh user
IdentityFileOffers these key files, in the order the config lists them
IdentityAgentAsks this agent socket instead of SSH_AUTH_SOCK
UserKnownHostsFileReads these files, and appends to the first one
GlobalKnownHostsFileReads these files
StrictHostKeyCheckingyes refuses an unknown host without asking, accept-new adds it without asking, ask (the default) asks
HashKnownHostsDecides whether a line joy appends is hashed

Two keys are refused by name rather than ignored: ProxyCommand and ProxyJump. libssh2 never opens a socket and libgit2 opens a plain TCP connection to the host in the URL, so a host that is only reachable through a jump cannot be reached at all. Instead of a DNS error that names the wrong cause, joy says: "This host uses ProxyJump in your ssh config. joy cannot run a proxy helper; use the https remote for this host or remove the rule."

Key files are checked before they reach the engine: an encrypted key with no passphrase available is skipped by name, and on Windows the OpenSSH format (openssh-key-v1) is rejected, because the Windows crypto backend libssh2 is built against reads classic PEM only and has no ed25519 or ECDSA at all. On that machine the token path is the working one.

known_hosts

libgit2 reads one file, ~/.ssh/known_hosts, and matches plain names byte for byte. Everything else your ssh setup uses is invisible to it, so joy does the whole host key check itself, over every file the keys above name:

  • ~/.ssh/known_hosts and ~/.ssh/known_hosts2, or whatever UserKnownHostsFile names;
  • /etc/ssh/ssh_known_hosts and /etc/ssh/ssh_known_hosts2, or whatever GlobalKnownHostsFile names.

joy reads plain patterns with *, ? and !, the bracketed [host]:port form for a port other than 22, and hashed entries (|1|salt|hash). Hashed entries are the normal case rather than the exception: Ubuntu ships HashKnownHosts yes in /etc/ssh/ssh_config. A @revoked line that matches host and key refuses the contact whatever else the files say. @cert-authority is not supported yet, and a host that presents a certificate host key is refused with a sentence that says so.

joy writes to exactly one file, the first UserKnownHostsFile, creating ~/.ssh with mode 0700 and the file with 0600 when they are missing, and it never rewrites a line that is already there.

One detail costs support calls, so it is checked up front: libssh2 stops at the first line it cannot parse and discards the whole file, which surfaces as "error reading known_hosts" about a file with hundreds of lines. joy validates ~/.ssh/known_hosts with the same rules before the first ssh contact of a run and names the offending line number.

Proxies

joy reads, in this order, and the first answer wins:

  1. remote.<name>.proxy from your Git configuration, for the remote joy is really contacting. This one is easy to forget and it beats everything below it;
  2. http.<url>.proxy, from the full remote URL down the path to the bare host, most specific first, and then plain http.proxy;
  3. https_proxy / HTTPS_PROXY for an https:// remote, http_proxy / HTTP_PROXY for an http:// one;
  4. ALL_PROXY / all_proxy.

An empty value ends the search where it stands, the way Git documents it: it means "no proxy for this one", not "keep looking".

NO_PROXY is evaluated by joy for every one of those sources, including the ones that come from Git configuration, and each entry is trimmed, so NO_PROXY="a.com, b.com" really does cover b.com.

Per operating system:

  • Linux and macOS: the sources above and nothing else. The macOS System Settings network proxy and the GNOME proxy settings are read by nothing in this stack. If you set only those, joy is offline as far as it is concerned, and the failure line says so.
  • Windows: the same sources, plus the machine wide WinHTTP proxy from the registry, which applies even when joy names no proxy of its own. Per user Internet Explorer or Edge settings, PAC files and WPAD are never used. When joy does compute a proxy, it replaces the registry one for that contact rather than merging with it.
  • The desktop app inherits no shell environment of its own, so joy imports HTTPS_PROXY, HTTP_PROXY, ALL_PROXY, NO_PROXY, SSL_CERT_FILE and SSL_CERT_DIR (lower and upper case) from your login shell, which is what makes the app and the CLI behave the same on one machine.

A proxy that asks for a password is answered through your credential helper and the credential is carried in the proxy URL in memory; it never reaches your Git configuration, a log line or an error text, which print host:port and nothing else.

Two limits, named rather than left to be discovered:

  • SOCKS proxies are not supported. joy refuses one before the contact, with a sentence that names the proxy URL and says it supports HTTP and HTTPS proxies only.
  • Off Windows, only Basic authentication reaches a proxy, because the engine is built without NTLM and Negotiate. On Windows, WinHTTP does Negotiate, NTLM, Digest and Basic. The failure text says which machine you are on.

Certificates and internal CAs

There are three trust stores and joy has no single CA setting of its own. What it honours:

  • Linux: OpenSSL's default verify paths, which means SSL_CERT_FILE and SSL_CERT_DIR when they are set, and otherwise /etc/ssl/certs/ca-certificates.crt and /etc/ssl/certs. A corporate CA installed with update-ca-certificates is trusted with no joy setting at all.
  • macOS: the system anchors plus the trust settings in Keychain Access. There is no file based alternative.
  • Windows: the Windows certificate store. A CA pushed by group policy works; there is no file based alternative.

One escape hatch exists and it is Linux only: ca_bundle and ca_dir in forges.yaml, and http.sslCAInfo and http.sslCAPath in your Git configuration, applied once when the process starts. On macOS and Windows each such entry is reported as ignored, with one sentence that names the entry, where it came from and the step that does work there (adding the CA to the login or System keychain, or having your administrator install it in the Windows certificate store).

What joy will not do, so that nobody plans around it:

  • joy never disables certificate verification. There is no joy equivalent of http.sslVerify=false.
  • joy does no client certificate. A forge or proxy that demands one fails with a sentence that says this.
  • joy reads none of http.sslVerify, http.sslBackend, http.sslCert, http.sslKey, GIT_SSL_CAINFO, GIT_SSL_CAPATH or GIT_SSL_NO_VERIFY.

Credential helpers

For an https remote, joy runs your Git credential helper itself. It reads credential.<remote url>.helper, then credential.<protocol>://<host>[:<port>].helper, then credential.helper, and it reads every value of a multi-valued key rather than only the last one, so an empty value resets the chain the way Git documents it.

A value that is an absolute path is run as it is. A short name such as manager is resolved to a binary called git-credential-manager: on Windows through the Git for Windows install path in the registry (mingw64\bin, mingw64\libexec\git-core and cmd below that install directory, plus whatever the registry's own LibexecPath names), elsewhere through libexec/git-core and lib/git-core beside the git on your PATH and then through PATH itself. joy never builds the string git credential-<name> and never runs git.

A value that starts with ! is shell shaped and is run under the Git for Windows usr\bin\sh.exe on Windows and under /bin/sh elsewhere, never under an sh picked off PATH.

joy runs get, and also store after a credential was accepted and erase after one was refused, so a credential your helper has revoked is not replayed on every contact. On a background or delegated host the helper is told not to ask: GCM_INTERACTIVE=never, GCM_GUI_PROMPT=0 and GIT_TERMINAL_PROMPT=0 are set for that one child process. The helper's own message is quoted in the failure line, for example "helper 'manager': fatal: Cannot prompt because user interactivity has been disabled."

The forge CLIs

joy never reads another CLI's credential store. Where it uses a forge CLI's credential, it asks that CLI, which is also the only way the CLI's own refresh runs, and the credential stays read only for joy: joy never refreshes it, never writes it and never revokes it.

ForgeCommand joy runsConfiguration joy looks for
GitHubgh auth token --hostname <host> [--user <login>]GH_CONFIG_DIR, XDG_CONFIG_HOME/gh, %AppData%\GitHub CLI, ~/.config/gh
GitLabglab auth credential-helper getGLAB_CONFIG_DIR, ~/.config/glab-cli, %LOCALAPPDATA%\glab-cli
Gitea, Forgejotea login helper getthe platform's XDG directory, then ~/.tea/tea.yml

Environment variables are read too, and the GitHub split is gh's own: a github.com token is never sent to somebody's Enterprise Server, and an Enterprise token never to github.com.

ForgeVariablesRead for
GitHubGH_TOKEN, GITHUB_TOKENgithub.com only
GitHub EnterpriseGH_ENTERPRISE_TOKEN, GITHUB_ENTERPRISE_TOKENevery other host
GitLabGITLAB_TOKENevery host
Gitea, ForgejoGITEA_TOKENevery host

Where joy keeps a credential of its own is on the Forges page.

What the git binary did that joy does not

For completeness, because these are the settings support will be asked about. The git binary honoured them; joy does not, by decision:

  • http.proxyAuthMethod with Digest, Negotiate and NTLM off Windows;
  • http.sslVerify, http.sslBackend, http.sslCert, http.sslKey and the GIT_SSL_* variables;
  • client certificates;
  • SOCKS proxies;
  • ProxyCommand and ProxyJump in your ssh config.

Every one of them is refused with a sentence that names the setting and the step that does work, rather than failing as a timeout or a DNS error.