# Git credentials for dashboard fetch/pull/push (issue #45, part A) ## Problem The dashboard runs git with no terminal and `GIT_TERMINAL_PROMPT=0`. When no credential helper on the machine holds a credential for the remote's host, git wants to prompt, cannot, and fails: `fatal: could not read Username for 'https://git.smartgift.io.vn': terminal prompts disabled`. Reproduced with `git -c credential.helper= push --dry-run`. ## Decisions (approved 2026-09-25) - AgentHub keeps its OWN credentials, shared by everyone using the AgentHub install (multi-user login is planned; accounts and settings are shared). - The dashboard asks for a credential when git needs one, then retries. - Merge and conflict resolution is part B and gets its own design. ## Design **Store** — `server/lib/git-credentials.js`, one file `/agenthub-git-credentials/credentials.json` (dir 0700, file 0600, written through a temp file), one record per `protocol + host`: `{protocol, host, username, password, savedAt}`. No route returns `password`; the list reports `hasToken`. Routes: `GET /api/git-credentials`, `DELETE /api/git-credentials/:protocol/:host`. **Delivery** — `server/lib/git-credential-helper.js`, a git credential helper speaking git's own protocol. For a network action, when AgentHub holds a credential for one of the repo's remote hosts, or the request carries one, git runs with `-c credential.helper= -c credential.helper=`: ONLY our helper. The machine's helpers are left out on purpose — a stale token there would win over the one just typed, and git's post-success `store` would copy a "use once" token into the OS user's own helper. With nothing from AgentHub for the host, the command is unchanged. The secret never reaches argv: the helper reads the store file, or `AGENTHUB_GIT_ONCE` in that one git process's env. **Flow** — an auth failure (`could not read Username|Password`, `Authentication failed`, `terminal prompts disabled`, `Invalid username or password`) answers `409 EGITAUTH` with `{protocol, host, username?}`. The git bar shows a form (username, password/token, "remember for everyone on this AgentHub", on by default) and resends the action with `credentials: {protocol, host, username, password, remember}`. The server accepts it only for a host that is one of the repo's remotes, runs the action, and saves the credential only after git succeeded and `remember` is set. **Limits** — one credential per host, so everyone on one AgentHub pushes as the same account. `fetch --all` over remotes on several hosts: a host with no AgentHub credential gets no machine helper either while one does. Over plain HTTP on a LAN the token crosses the network unencrypted; the form says so. ## Testing Store: save/list/remove, 0600, list never carries the password, bad host refused. Helper: real `git credential fill` through our helper, stored and once. Route: a local HTTP remote answering 401 → `EGITAUTH` with the host; a resend with credentials reaches that server as Basic auth; nothing saved on failure. Client: the form appears on `EGITAUTH`, resends, never renders a token.