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:
| Key | What joy does with it |
|---|---|
HostName | Contacts this name instead of the alias you wrote in the remote |
Port | Contacts this port |
User | Uses this ssh user |
IdentityFile | Offers these key files, in the order the config lists them |
IdentityAgent | Asks this agent socket instead of SSH_AUTH_SOCK |
UserKnownHostsFile | Reads these files, and appends to the first one |
GlobalKnownHostsFile | Reads these files |
StrictHostKeyChecking | yes refuses an unknown host without asking, accept-new adds it without asking, ask (the default) asks |
HashKnownHosts | Decides 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_hostsand~/.ssh/known_hosts2, or whateverUserKnownHostsFilenames;/etc/ssh/ssh_known_hostsand/etc/ssh/ssh_known_hosts2, or whateverGlobalKnownHostsFilenames.
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:
remote.<name>.proxyfrom your Git configuration, for the remote joy is really contacting. This one is easy to forget and it beats everything below it;http.<url>.proxy, from the full remote URL down the path to the bare host, most specific first, and then plainhttp.proxy;https_proxy/HTTPS_PROXYfor anhttps://remote,http_proxy/HTTP_PROXYfor anhttp://one;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_FILEandSSL_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_FILEandSSL_CERT_DIRwhen they are set, and otherwise/etc/ssl/certs/ca-certificates.crtand/etc/ssl/certs. A corporate CA installed withupdate-ca-certificatesis 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_CAPATHorGIT_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.
| Forge | Command joy runs | Configuration joy looks for |
|---|---|---|
| GitHub | gh auth token --hostname <host> [--user <login>] | GH_CONFIG_DIR, XDG_CONFIG_HOME/gh, %AppData%\GitHub CLI, ~/.config/gh |
| GitLab | glab auth credential-helper get | GLAB_CONFIG_DIR, ~/.config/glab-cli, %LOCALAPPDATA%\glab-cli |
| Gitea, Forgejo | tea login helper get | the 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.
| Forge | Variables | Read for |
|---|---|---|
| GitHub | GH_TOKEN, GITHUB_TOKEN | github.com only |
| GitHub Enterprise | GH_ENTERPRISE_TOKEN, GITHUB_ENTERPRISE_TOKEN | every other host |
| GitLab | GITLAB_TOKEN | every host |
| Gitea, Forgejo | GITEA_TOKEN | every 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.proxyAuthMethodwith Digest, Negotiate and NTLM off Windows;http.sslVerify,http.sslBackend,http.sslCert,http.sslKeyand theGIT_SSL_*variables;- client certificates;
- SOCKS proxies;
ProxyCommandandProxyJumpin 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.