Configuration & first run
How to point madtea at your forge, where it stores credentials, and how the override precedence resolves. This guide assumes the madtea binary is already installed - see install.md if not. For what scopes to enable on the token, see PERMISSIONS.md. For how madtea stores the token and the credential-safety guarantees, see credential-safety.md.
First run
madtea auth login walks you through entering your forge URL and pasting an API token, then stores the token securely in your OS keychain (or pass, or systemd-creds on a headless server, when no keychain is available). Run it once globally and every repo on that forge is covered:
madtea auth login --global # interactive: enter forge URL + paste an API token
madtea auth whoami # verify: confirms the token against the server, prints identity + token scopes (masked)
madtea issue list # first real call — exercises the actual workflow
When run inside a repo, madtea auth login auto-detects the forge URL from your origin remote. When prompted for token scopes, Standard covers typical day-to-day work (create PRs/issues, merge, comment); see PERMISSIONS.md for the read-only / standard / full tiers and exactly what each scope unlocks. A 403 later means a missing scope — the error names which one.
Setup modes
madtea auth login # interactive; forge-wide - one login covers every repo on this forge
madtea auth login --web # browser OAuth login (no manual token)
madtea auth login --url URL --token TOKEN # non-interactive (CI)
madtea auth login --insecure-storage # force plaintext git-config token storage
Browser OAuth login (--web)
madtea auth login --web authenticates through your browser instead of a pasted token. It runs an OAuth login (authorization code + PKCE) against the forge, then mints a long-lived API token for you and stores it through the same secure-backend precedence as the paste path. The result is an ordinary personal access token — you can see it later in the forge’s Applications settings, named madtea-<timestamp>. The minted token gets a least-privilege scope set: the Standard tier plus read:organization (for org-label name resolution); see PERMISSIONS.md for the tier tables and exactly what each scope unlocks. If you need broader scope, mint a token manually in the forge web UI and use the paste-based madtea auth login.
gh and glab ship a built-in OAuth application identity for their own forge, so their auth login --web needs no setup. Gitea and Forgejo have no registered madtea application, so --web uses an OAuth app you register once per forge. Its client id is stored per-forge in your git config next to the URL; it is not a secret (public OAuth clients have none), so it lives in plaintext git config, not the secure backend. Register it once:
- In the forge web UI, open Settings → Applications → Manage OAuth2 Applications and create a new application.
- Name it
madtea, set the redirect URI to exactlyhttp://127.0.0.1, and uncheck “Confidential client” — it must be a public client. - Copy the generated client id. The first
madtea auth login --webrun prompts for it and saves it; later runs reuse it.
--web needs an interactive terminal and the forge’s OAuth2 provider. It falls back to the token-paste prompt when the provider is disabled or when the server rejects OAuth-authenticated token creation (current Forgejo). On a machine with no browser, --web prints the authorize URL for manual opening and falls back to token paste only after the browser-wait times out. For headless or CI environments, use madtea auth login --url URL --token TOKEN instead.
Once a
madteaapplication is upstreamed into Gitea’s and Forgejo’s built-in OAuth defaults, this one-time registration will no longer be needed (tracked as a follow-up).
By default the token is stored in your OS keychain (or pass when no keychain is available, or systemd-creds on a headless server with systemd ≥ 256 — encrypted at rest, TPM-sealed when a TPM is present). When no secure backend is present, madtea refuses to store the token — plaintext git config (file mode 0600) is never a fallback and happens only when you explicitly pass --insecure-storage, which also forces the plaintext path when a secure backend IS available — see credential-safety.md for the backends and what each guarantees.
Storage location vs. token scope — two independent choices. Where credentials live is the storage location (the secure backend keyed per forge host, with git config resolved by the precedence below). What the token may do is its permission scope (read-only / standard / full, in
PERMISSIONS.md). This documentation uses the word “scope” only for token permissions, never for storage location.
Which forge?
Shorthand works the way gh users expect, on a problem gh never has: madtea clone acme/api (no URL) targets the forge of wherever you stand - inside a clone, its origin; in a workspace folder, the forge your existing clones agree on. gh only ever means github.com, and tea makes you name a configured login; madtea infers across forges, names the host it resolved, and when the answer is genuinely ambiguous it refuses and lists the candidates instead of guessing.
A Codeberg clone and a self-hosted Forgejo clone side by side can each talk to their own forge as their own identity, and auth whoami always answers for where you stand. The guess, precisely:
which forge?
1. MADTEA_URL in the environment? -> use it
2. inside a clone (any subdir)? -> its madtea.url override, else its origin's host
3. folder's child clones scanned:
all one host -> that host
hosts disagree, global set -> global madtea.url breaks the tie
hosts disagree, nothing set -> refuse, naming the hosts found
4. no clones at all -> global madtea.url
Whichever host wins, only that host’s keychain token is used - resolution picks a forge, never shops a token around. You rarely set madtea.url yourself: auth login records it, and clone / git remote add stamp it into each new clone; setting it by hand on a repo is the escape hatch for a forge not served at its git host.
Override precedence
Credentials resolve in this order, first match wins:
- Environment variables —
MADTEA_URL,MADTEA_TOKEN,MADTEA_USER,MADTEA_REPO - Secure backend (OS keychain,
pass, orsystemd-creds), when the resolved git config records one - Per-repo git config (
<repo>/.git/config) - Global git config (
~/.gitconfig)
Whether a token lives in the secure backend or in plaintext is recorded per git scope, so git’s own precedence applies between scopes: a per-repo setup overrides a global one regardless of which stores securely. A per-repo plaintext setup therefore takes precedence over a global secure setup for that repo (it does not change the global setup, which still covers every other repo).
Token scopes
Token scopes for typical use: read:repository, write:repository, read:issue, write:issue, read:notification, write:notification, read:user. The full scope reference and the read-only / standard / full tiers are in PERMISSIONS.md.
Logging out
madtea auth logout clears all madtea-stored credentials for a scope:
madtea auth logout # clear this repo's credentials (local scope)
madtea auth logout --global # clear global (~/.gitconfig) credentials
It removes every madtea.* git-config key in the chosen scope and the associated secure-backend entry (OS keychain, pass, or systemd-creds) identified by the madtea.url + madtea.user values read from that scope before any deletion. A missing backend entry is a non-fatal warning. Non-madtea keys (user.name, user.email, credential.helper, etc.) are never touched.
This action cannot be undone. The command requires an interactive terminal and prompts for confirmation — no --yes/--force bypass is provided. Without --global, a git repository must be present in the current directory.
SSH remotes
madtea’s credential model is https + host-scoped GIT_ASKPASS (ADR 0013): every remote-touching git operation injects a temporary credential helper that answers git’s prompts for the configured forge host and nothing else. An SSH origin (git@host:owner/repo.git) routes around this model entirely — ssh-agent or an SSH key carries authentication, git never issues a credential prompt, GIT_ASKPASS never fires, and the stored token is never offered to an SSH remote. SSH origins work transparently when a usable key and a known host key are already in place.
Flipping between HTTPS and SSH. Use madtea auth use-ssh (or madt_auth_use_ssh from the MCP server) to toggle a remote’s URL between the two forms. The flip is reversible — call again to switch back:
madtea auth use-ssh # flip origin (https ↔ ssh)
madtea auth use-ssh --remote upstream # flip a non-origin remote
madtea auth use-ssh --dir /path/to/repo # target a specific repo
SSH auth is user-managed (ssh-agent, ~/.ssh/config, SSH keys); madtea does not manage SSH keys on your behalf. After flipping to SSH, token-based GIT_ASKPASS injection no longer applies to that remote.
Failure mode (known host key, missing or rejected key). If ssh cannot authenticate, git surfaces the error verbatim:
Permission denied (publickey).
Clean and diagnosable — there is no credential scraping or fallback prompt.
First-contact caveat: unknown host key. On the first connection to a host, ssh’s default StrictHostKeyChecking=ask prompts for host-key confirmation on stdin. The MCP server path injects GIT_SSH_COMMAND="ssh -o BatchMode=yes" so that an unknown host key fails fast with a clear error (Host key verification failed.) instead of hanging the call path. The interactive CLI does not inject BatchMode and will prompt the user as normal.
Pre-populate ~/.ssh/known_hosts before using an SSH remote from the MCP server:
ssh -o StrictHostKeyChecking=accept-new -T git@<forge-host>
# exits non-zero (normal for git accounts that don't open a shell); host key is now cached
Or keep origins on https — madtea clone owner/repo always produces an https origin with automatic credential injection and avoids the SSH setup entirely.
Wiring madtea into an agent (and the safety hooks)
For Claude Code, madtea install is the single setup command: it installs the binary, shell completions, and registers the madtea MCP server (running claude mcp add when the claude CLI is on PATH). For other agents, add --mcp-client {codex,antigravity} (repeatable or comma-separated) to wire them in the same run. madtea mcp-config only prints a snippet to paste manually. See clients for the per-agent details.
madtea’s seven agent-safety PreToolUse hooks — the guards that block raw git which would leak a credential-embedding remote URL or bypass the madtea workflow — ship with the Claude Code plugin. The binary does not materialize them; install the plugin to add them (madtea install prints a one-line pointer when Claude Code is present). The hooks are Claude-Code-specific — codex/antigravity clients get none. Full detail in safety-hooks.md.
Canonical source: docs/guides/setup.md in the madtea repo.