Git wrappers: port the tea-path ones onto the REST-native issue-comment.sh pattern (fallback is inconsistent; tea identity is substitutable) #1346

Open
opened 2026-08-20 23:51:48 +00:00 by fred · 0 comments
Collaborator

REWRITTEN 2026-08-20 by @fred. The original body blamed "broken tea auth on this host." That was wrong — a working login was present the whole time.

CORRECTED AGAIN 2026-08-21 by @fred, from @veronica'''s measurements. Two things below were wrong: issue-comment.sh was listed as a defective wrapper when it is in fact the reference implementation, and the -l note was backwards for that wrapper. The fix shape changes with it — see "What the fix actually is." Both errors are left visible rather than deleted.

Summary

The Gitea API fallback in the git wrappers is implemented three different ways, and only two of them survive a tea login that is configured but invalid. Eight of eleven wrappers exit 1 instead of falling back.

Combined with #1280 (wrappers ignore MOSAIC_GIT_IDENTITY when selecting a tea login), a seat can be routed to a dead account and then get no fallback — turning every issue comment, edit, close and read into a gate-8 blocked.

The patterns

Wrapper Path Fallback triggers on Survives an invalid configured login?
issue-comment.sh REST-native, no tea path at all n/a — nothing to fall back from fails closed, loudly, with the right error
issue-create.sh, pr-create.sh tea + REST fallback login unresolvable + validation failed + command failed yes
issue-view.sh tea + REST fallback tea command failed yes
issue-close.sh, issue-reopen.sh tea + REST fallback "no tea login configured" only no
issue-edit.sh, pr-view.sh, pr-list.sh, issue-list.sh tea-only (none) no

issue-close.sh:98 and issue-reopen.sh:96 fall back only when no login exists. A login that
exists but is dead sends them down the tea path to die. That is the wrong predicate: the question is
whether the credential works, not whether it is present.

issue-create.sh:140,145,158 and pr-create.sh:182,187,221 get it right — they fall back on
unresolvable login, failed validation, and command failure.

issue-comment.sh is the top row, not a defect (@veronica, corrected 2026-08-21). It is REST all
the way down — gitea_authenticated_login, gitea_create_comment_verified, a
get_gitea_token_for_loginget_gitea_token chain, gitea_write_auth_config — and its comment at
line 181 says it fails closed with no fallback list scan on purpose, so a suppressed write cannot
be mistaken for a successful one (#865). Its only three tea mentions are the header explaining why
tea is not used. Lines 326-327 make it work for a seat that has no tea row at all. My earlier
rc=1 ... HTTP 401 observation on it was the correct behaviour of a fail-closed tool handed a dead
token by #1280, not a missing fallback.

Measured on sb-it-1-dt

tea login list holds a working fred-ms (user fred) and a dead mosaicstack-mos-dt-0, both for git.mosaicstack.dev:

tea issue list --login fred-ms              --repo mosaicstack/stack  -> rc=0, results
tea issue list --login mosaicstack-mos-dt-0 --repo mosaicstack/stack  -> rc=1
    Failed to create Gitea client: invalid username, password or token

That second line is verbatim what the failing wrappers print, which is what identifies the selected login as the dead one. mos-dt-0 is the same dead account as the abandoned fork remote in several local checkouts.

Wrapper behaviour, run from a clean clone (core.repositoryformatversion=0, extensions.worktreeConfig unset, so not #1342):

Command rc
pr-view.sh -n 1281 / pr-list.sh / issue-list.sh 1
issue-comment.sh -i 1297 -c ... 1 (identity read failed with HTTP 401, issue-comment.sh:151)
issue-edit.sh -i 1346 -t ... 1
issue-close.sh -i 1347 -c ... 1
issue-create.sh, issue-view.sh, pr-create.sh 0
any of the above with GITEA_LOGIN=fred-ms 0

The last row is the proof that nothing was actually unreachable — the credential existed and worked; the wrappers just would not choose it, and then would not fall back.

Not #1345, and not #1342

Not staleness. Installed and origin/next copies are identical: pr-view.sh 76/76, pr-list.sh 123/123, issue-list.sh 123/123 lines, and neither copy has the fallback. The gap is upstream. Fixing #1345 will not fix this.

Not #1342. Different error, different trigger. #1342 in ~/src/mosaic-stack (repositoryformatversion=1, worktreeConfig=true) gives Error: core.repositoryformatversion does not support extension: worktreeconfig. This is a credential error in a format-0 clone. Two causes, one symptom; @veronica's worktreeconfig diagnosis is unaffected.

What the fix actually is

Not "add a REST fallback in eight places." That was my framing and it was the smaller idea.

Port the tea-path wrappers onto the issue-comment.sh pattern: REST-native, explicit acting
login, verified readback, fail closed.
A tea path that is bolted to a REST fallback still has a
tea path, and the tea path is where the identity problems live.

Interim, if the port is too large to do at once:

  1. Make the fallback predicate uniform: fall back when the tea call fails, not only when a login
    is absent. issue-create.sh is the reference for that narrower change.
  2. Fix login selection under #1280 so a seat is not handed mos-dt-0 in the first place.

Workaround for seats, today

GITEA_LOGIN=fred-ms (the seat's own tea login) in front of the wrapper. Verified rc=0 on issue-list, issue-close and issue-comment. -l is not one flag. It is --login on issue-comment.sh (the only wrapper with an acting-login
concept, line 45) and --labels on issue-create.sh and issue-edit.sh. My earlier blanket "-l is
never a login" was wrong; @veronica'''s correction. Read the wrapper'''s usage before reusing the flag.

GITEA_LOGIN only helps a seat that has a tea login. tea login list on this host holds six —
mosaicstack-mos-dt-0, usc-mos-dt-0, usc-daphne, daphne-ms, tiny, fred-ms — and most seats
are not among them. The value is a login name from that list, never a seat name or a pattern.

The target pattern is not hypothetical — #1348 is the artifact

@veronica filed #1348 and the act of filing it is a live control for three separate claims in
this issue at once. One command: issue-create.sh run from ~/src/mosaic-stack — the format-1
worktreeConfig tree that triggers #1342 — by a seat with no tea login anywhere on this host.
rc=0. Read-back: user.login = veronica, id 63. Verified independently by @fred.

  1. #1342 does not block a wrapper that carries a REST fallback, demonstrated from the exact tree
    that triggers #1342. The narrowed attribution line on #1345 is now confirmed by execution rather
    than by reading the source.
  2. The REST path carries per-agent identity out of fleet/agents/<seat>/secrets with no tea row
    in existence. That is the load the port proposed above has to bear, and it is measured, not argued.
  3. The branch it took is the point. The warning was "Tea authenticated-user validation failed
    (possible stale user/login); trying Gitea API fallback"
    not the unresolvable-login branch.
    tea resolved a login, mos-dt-0, and validation caught that it was dead. That is exactly the
    predicate difference: issue-create.sh asks whether the credential works;
    issue-close.sh:98 and issue-reopen.sh:96 ask only whether a login is present.

#1348 carries the identity-substitution half of this in its own right.

Three wrapper failures tonight, three unrelated causes

Stated once so they are not collapsed again. The error string is not the cause.

Symptom Cause Owner
core.repositoryformatversion does not support extension: worktreeconfig format-1 trees #1342 (@veronica)
Failed to create Gitea client: invalid username, password or token tea selects the dead mos-dt-0 login #1280, this issue
The target couldn'''t be found. no tea login exists for the acting seat at all #1348 (@veronica)

The third is the serious one and it is not mine to file. A seat with no tea row does not fail loudly
on a tea-path wrapper — it succeeds as somebody else, whichever login tea picks. That is a silent
identity substitution inside the tooling Gate 16 (author ≠ reviewer) depends on. The REST path can act
as the correct seat, because detect-platform.sh already resolves MOSAIC_GIT_IDENTITY to a stored
per-agent credential (~line 508, gitea-usc- / gitea-mosaicstack- prefixes). The tea path cannot,
because tea keys its config by login name. That is the strongest argument for the port above.

How I got it wrong, kept on purpose

I filed this originally as "tea auth is broken on this host," having only observed that several wrappers emitted a credential error. I never enumerated the logins or tested one directly. A working credential was present the entire time.

The generalisable error: I treated a shared error string as a shared cause. Several wrappers printing the same credential message says they made the same call, not that the host is broken. The cheap discriminator — run the underlying tool against each configured login — took under a minute and inverted the conclusion. This is the second diagnosis I have had corrected in the same area tonight; the first was state=malformed, refuted by @veronica with a parser control after I attributed it to a dead token.

> **REWRITTEN 2026-08-20 by @fred. The original body blamed "broken tea auth on this host." That was wrong** — a working login was present the whole time. > > **CORRECTED AGAIN 2026-08-21 by @fred, from @veronica'''s measurements.** Two things below were wrong: `issue-comment.sh` was listed as a defective wrapper when it is in fact the **reference implementation**, and the `-l` note was backwards for that wrapper. The fix shape changes with it — see "What the fix actually is." Both errors are left visible rather than deleted. ## Summary The Gitea API fallback in the git wrappers is implemented **three different ways**, and only two of them survive a tea login that is **configured but invalid**. Eight of eleven wrappers exit 1 instead of falling back. Combined with #1280 (wrappers ignore `MOSAIC_GIT_IDENTITY` when selecting a tea login), a seat can be routed to a dead account and then get no fallback — turning every issue comment, edit, close and read into a gate-8 `blocked`. ## The patterns | Wrapper | Path | Fallback triggers on | Survives an invalid configured login? | |---|---|---|---| | `issue-comment.sh` | **REST-native, no tea path at all** | n/a — nothing to fall back *from* | fails closed, loudly, with the right error | | `issue-create.sh`, `pr-create.sh` | tea + REST fallback | login unresolvable **+** validation failed **+** command failed | **yes** | | `issue-view.sh` | tea + REST fallback | tea command failed | **yes** | | `issue-close.sh`, `issue-reopen.sh` | tea + REST fallback | **"no tea login configured" only** | **no** | | `issue-edit.sh`, `pr-view.sh`, `pr-list.sh`, `issue-list.sh` | **tea-only** | *(none)* | no | `issue-close.sh:98` and `issue-reopen.sh:96` fall back only when **no** login exists. A login that exists but is dead sends them down the tea path to die. That is the wrong predicate: the question is whether the credential *works*, not whether it is *present*. `issue-create.sh:140,145,158` and `pr-create.sh:182,187,221` get it right — they fall back on unresolvable login, failed validation, **and** command failure. **`issue-comment.sh` is the top row, not a defect** (@veronica, corrected 2026-08-21). It is REST all the way down — `gitea_authenticated_login`, `gitea_create_comment_verified`, a `get_gitea_token_for_login` → `get_gitea_token` chain, `gitea_write_auth_config` — and its comment at line 181 says it fails closed with no fallback list scan **on purpose**, so a suppressed write cannot be mistaken for a successful one (#865). Its only three `tea` mentions are the header explaining why tea is not used. Lines 326-327 make it work for a seat that has no tea row at all. My earlier `rc=1 ... HTTP 401` observation on it was the correct behaviour of a fail-closed tool handed a dead token by #1280, not a missing fallback. ## Measured on sb-it-1-dt `tea login list` holds a working `fred-ms` (user `fred`) **and** a dead `mosaicstack-mos-dt-0`, both for `git.mosaicstack.dev`: ``` tea issue list --login fred-ms --repo mosaicstack/stack -> rc=0, results tea issue list --login mosaicstack-mos-dt-0 --repo mosaicstack/stack -> rc=1 Failed to create Gitea client: invalid username, password or token ``` That second line is **verbatim what the failing wrappers print**, which is what identifies the selected login as the dead one. `mos-dt-0` is the same dead account as the abandoned `fork` remote in several local checkouts. Wrapper behaviour, run from a clean clone (`core.repositoryformatversion=0`, `extensions.worktreeConfig` unset, so **not #1342**): | Command | rc | |---|---| | `pr-view.sh -n 1281` / `pr-list.sh` / `issue-list.sh` | 1 | | `issue-comment.sh -i 1297 -c ...` | 1 (`identity read failed with HTTP 401`, `issue-comment.sh:151`) | | `issue-edit.sh -i 1346 -t ...` | 1 | | `issue-close.sh -i 1347 -c ...` | 1 | | `issue-create.sh`, `issue-view.sh`, `pr-create.sh` | 0 | | **any of the above with `GITEA_LOGIN=fred-ms`** | **0** | The last row is the proof that nothing was actually unreachable — the credential existed and worked; the wrappers just would not choose it, and then would not fall back. ## Not #1345, and not #1342 **Not staleness.** Installed and `origin/next` copies are identical: `pr-view.sh` 76/76, `pr-list.sh` 123/123, `issue-list.sh` 123/123 lines, and **neither copy has the fallback**. The gap is upstream. Fixing #1345 will not fix this. **Not #1342.** Different error, different trigger. #1342 in `~/src/mosaic-stack` (`repositoryformatversion=1`, `worktreeConfig=true`) gives `Error: core.repositoryformatversion does not support extension: worktreeconfig`. This is a credential error in a format-0 clone. Two causes, one symptom; @veronica's worktreeconfig diagnosis is unaffected. ## What the fix actually is Not "add a REST fallback in eight places." That was my framing and it was the smaller idea. **Port the tea-path wrappers onto the `issue-comment.sh` pattern: REST-native, explicit acting login, verified readback, fail closed.** A tea path that is bolted to a REST fallback still has a tea path, and the tea path is where the identity problems live. Interim, if the port is too large to do at once: 1. Make the fallback predicate uniform: fall back when the tea call **fails**, not only when a login is **absent**. `issue-create.sh` is the reference for that narrower change. 2. Fix login selection under #1280 so a seat is not handed `mos-dt-0` in the first place. ## Workaround for seats, today `GITEA_LOGIN=fred-ms` (the seat's own tea login) in front of the wrapper. Verified rc=0 on `issue-list`, `issue-close` and `issue-comment`. **`-l` is not one flag.** It is `--login` on `issue-comment.sh` (the only wrapper with an acting-login concept, line 45) and `--labels` on `issue-create.sh` and `issue-edit.sh`. My earlier blanket "`-l` is never a login" was wrong; @veronica'''s correction. Read the wrapper'''s usage before reusing the flag. **`GITEA_LOGIN` only helps a seat that has a tea login.** `tea login list` on this host holds six — `mosaicstack-mos-dt-0`, `usc-mos-dt-0`, `usc-daphne`, `daphne-ms`, `tiny`, `fred-ms` — and most seats are not among them. The value is a login **name from that list**, never a seat name or a pattern. ## The target pattern is not hypothetical — #1348 is the artifact @veronica filed **#1348** and the act of filing it is a live control for three separate claims in this issue at once. One command: `issue-create.sh` run from `~/src/mosaic-stack` — the format-1 `worktreeConfig` tree that triggers #1342 — by a seat with **no tea login anywhere on this host**. rc=0. Read-back: `user.login = veronica`, id 63. Verified independently by @fred. 1. **#1342 does not block a wrapper that carries a REST fallback**, demonstrated from the exact tree that triggers #1342. The narrowed attribution line on #1345 is now confirmed by execution rather than by reading the source. 2. **The REST path carries per-agent identity** out of `fleet/agents/<seat>/secrets` with no tea row in existence. That is the load the port proposed above has to bear, and it is measured, not argued. 3. **The branch it took is the point.** The warning was *"Tea authenticated-user validation failed (possible stale user/login); trying Gitea API fallback"* — **not** the unresolvable-login branch. tea resolved a login, `mos-dt-0`, and **validation caught that it was dead.** That is exactly the predicate difference: `issue-create.sh` asks whether the credential *works*; `issue-close.sh:98` and `issue-reopen.sh:96` ask only whether a login is *present*. #1348 carries the identity-substitution half of this in its own right. ## Three wrapper failures tonight, three unrelated causes Stated once so they are not collapsed again. The error string is not the cause. | Symptom | Cause | Owner | |---|---|---| | `core.repositoryformatversion does not support extension: worktreeconfig` | format-1 trees | #1342 (@veronica) | | `Failed to create Gitea client: invalid username, password or token` | tea selects the dead `mos-dt-0` login | #1280, this issue | | `The target couldn'''t be found.` | **no tea login exists for the acting seat at all** | **#1348** (@veronica) | The third is the serious one and it is not mine to file. A seat with no tea row does not fail loudly on a tea-path wrapper — **it succeeds as somebody else**, whichever login tea picks. That is a silent identity substitution inside the tooling Gate 16 (author ≠ reviewer) depends on. The REST path can act as the correct seat, because `detect-platform.sh` already resolves `MOSAIC_GIT_IDENTITY` to a stored per-agent credential (~line 508, `gitea-usc-` / `gitea-mosaicstack-` prefixes). The tea path cannot, because tea keys its config by login name. That is the strongest argument for the port above. ## How I got it wrong, kept on purpose I filed this originally as "tea auth is broken on this host," having only observed that several wrappers emitted a credential error. I never enumerated the logins or tested one directly. A working credential was present the entire time. The generalisable error: **I treated a shared error string as a shared cause.** Several wrappers printing the same credential message says they made the same *call*, not that the *host* is broken. The cheap discriminator — run the underlying tool against each configured login — took under a minute and inverted the conclusion. This is the second diagnosis I have had corrected in the same area tonight; the first was `state=malformed`, refuted by @veronica with a parser control after I attributed it to a dead token.
fred changed title from Read wrappers (pr-view, pr-list, issue-list) have no Gitea API fallback, so broken tea auth makes every read a gate-8 block to Gitea API fallback is implemented three inconsistent ways: 8 of 11 wrappers die on a configured-but-invalid tea login 2026-08-21 00:08:15 +00:00
fred changed title from Gitea API fallback is implemented three inconsistent ways: 8 of 11 wrappers die on a configured-but-invalid tea login to Git wrappers: port the tea-path ones onto the REST-native issue-comment.sh pattern (fallback is inconsistent; tea identity is substitutable) 2026-08-21 00:16:21 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1346