A revoked git credential only surfaces when a push fails #21

Closed
opened 2026-08-28 17:09:53 +09:00 by floot · 2 comments
Owner

Applying a NixOS or Endo change restarts the daemon, which re-mints the
Forgejo credential. Remotes created before the restart still hold the old one,
and nothing indicates this until a push is attempted:

Error: Git credential for "https://git.goooooo.ooo" has been revoked

This happened twice in session mt82q1ns-l8nrge, even though the agent prompt
explicitly warns about it — a warning does not help when the state is
invisible. list() shows the remote as if it were fine, and
provideGitRemote has to be re-run from memory of a prompt paragraph.

Adjacent papercuts in the same area:

  • provideGitRemote with an HTTPS url and no credential fails with
    GitRemote HTTPS remotes require a Git credential cap — good message, but
    the requirement is not visible before the call.
  • git log <rev> gave fatal: bad object five times because the revision
    existed on Forgejo but not yet in the local clone. Nothing suggests a fetch.

Wanted: a remote or credential should report its own liveness — a state
field on list(), or an auto-remint on first use after a restart, rather than
failing at push time.

Applying a NixOS or Endo change restarts the daemon, which re-mints the Forgejo credential. Remotes created before the restart still hold the old one, and nothing indicates this until a push is attempted: ``` Error: Git credential for "https://git.goooooo.ooo" has been revoked ``` This happened twice in session `mt82q1ns-l8nrge`, even though the agent prompt explicitly warns about it — a warning does not help when the state is invisible. `list()` shows the remote as if it were fine, and `provideGitRemote` has to be re-run from memory of a prompt paragraph. Adjacent papercuts in the same area: - `provideGitRemote` with an HTTPS url and no credential fails with `GitRemote HTTPS remotes require a Git credential cap` — good message, but the requirement is not visible before the call. - `git log <rev>` gave `fatal: bad object` five times because the revision existed on Forgejo but not yet in the local clone. Nothing suggests a fetch. **Wanted:** a remote or credential should report its own liveness — a `state` field on `list()`, or an auto-remint on first use after a restart, rather than failing at push time.
Author
Owner

Partly addressed, deliberately left open.

The practical failure is gone: the credential the machine-admin session pushes with is now granted from the root inventory (9dd1371) and rotated in place on every start (623cb02), so a restart no longer silently strands it. Generation 44 came up with a live credential and no intervention. Because a GitRemote holds the credential CAP rather than its name, rotation revives remotes configured in an earlier process instead of leaving them holding a corpse.

What this issue actually asks for is still missing: nothing surfaces a revoked credential before a push fails. GitCredentialController.inspect() reports { available, revoked }, but no health check or session-start probe reads it, so an agent still discovers revocation by trying to push. Leaving open for that.

Partly addressed, deliberately left open. The practical failure is gone: the credential the machine-admin session pushes with is now granted from the root inventory (9dd1371) and rotated in place on every start (623cb02), so a restart no longer silently strands it. Generation 44 came up with a live credential and no intervention. Because a GitRemote holds the credential CAP rather than its name, rotation revives remotes configured in an earlier process instead of leaving them holding a corpse. What this issue actually asks for is still missing: nothing surfaces a revoked credential before a push fails. `GitCredentialController.inspect()` reports `{ available, revoked }`, but no health check or session-start probe reads it, so an agent still discovers revocation by trying to push. Leaving open for that.
Author
Owner

Closing: both halves are now done and verified across restarts.

The silent-death half: the credential is granted to the factory profile from the root inventory (9dd1371) and rotated in place on every start (623cb02). Generations 44, 45 and 46 each came up with { available: true, revoked: false } and a working fetch through a remote configured before the restart, with no intervention. Because a GitRemote holds the credential CAP, rotation revives those remotes rather than stranding them.

The surfacing half this issue actually asked for: GitRemote.credentialHealth() (cbc261ab, live at generation 46) reports { required, kind, audience, available, revoked } without using the credential, so a holder can check before pushing instead of learning from a failed push. Live output on this host:

{ "required": true, "kind": "basic", "audience": "https://git.goooooo.ooo",
  "available": true, "revoked": false }

It is a separate method rather than a field on inspect(), because a test requires that snapshot to carry nothing credential-shaped, and no material is exposed either way.

Closing: both halves are now done and verified across restarts. The silent-death half: the credential is granted to the factory profile from the root inventory (9dd1371) and rotated in place on every start (623cb02). Generations 44, 45 and 46 each came up with `{ available: true, revoked: false }` and a working fetch through a remote configured before the restart, with no intervention. Because a GitRemote holds the credential CAP, rotation revives those remotes rather than stranding them. The surfacing half this issue actually asked for: `GitRemote.credentialHealth()` (cbc261ab, live at generation 46) reports `{ required, kind, audience, available, revoked }` without using the credential, so a holder can check before pushing instead of learning from a failed push. Live output on this host: ```json { "required": true, "kind": "basic", "audience": "https://git.goooooo.ooo", "available": true, "revoked": false } ``` It is a separate method rather than a field on `inspect()`, because a test requires that snapshot to carry nothing credential-shaped, and no material is exposed either way.
floot closed this issue 2026-09-01 14:25:01 +09:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
floot/endo#21
No description provided.