feat(tools/git): explain tea's misleading user does not exist error (stale token, not a missing account) #1086

Merged
Mos merged 4 commits from feat/1082-tea-stale-token-diagnostic into main 2026-08-07 05:07:41 +00:00
Contributor

Implements #1082.

The problem

tea reports user does not exist [uid: 0, name: ] when the token is revoked or stale. The message reads as "that account isn't there"; the actual cause is that tea login keeps its own copy of the token, so rotating the credential store leaves the login pointing at a dead credential.

The error points at the account. The cause is the cached credential. That gap costs debugging time every time it happens.

What this adds

explain_tea_user_does_not_exist() in detect-platform.sh, invoked from the three tea-failure fallback paths in issue-view.sh, issue-create.sh and pr-create.sh:

declare -F explain_tea_user_does_not_exist >/dev/null && explain_tea_user_does_not_exist

The declare -F guard makes each call site independent of load order and a safe no-op if the helper is ever not sourced.

Purely additive: stderr only, no control flow changes, no gate changes, no credential handling touched. 18 lines, 15 of them the helper.

Verified both directions

helper sourced        -> emits the NOTE block on stderr
helper NOT sourced    -> guarded call is a silent no-op, exit 1, no output

Plus bash -n clean on all four files.

The second case is the one that matters: the guard has to be a genuine no-op, not merely untested.

Placement — and a question for the reviewer

I put the call at the generic tea-failure site in each file (Warning: tea <verb> failed, trying Gitea API fallback...), since that is where any tea failure lands, including this one.

Worth noting: main already has more specific messages that this PR does not touch —

issue-create.sh:145   Warning: Tea authenticated-user validation failed (possible stale user/login); ...
pr-create.sh:187      Warning: Tea authenticated-user validation failed (possible stale user/login); ...

Those already name the cause. What they don't give is the remedy — which cached copy to check and how to re-register. If a reviewer prefers the explainer at those sites instead of (or in addition to) the generic ones, that's a reasonable call and I'll move it. I chose the generic sites to mirror the original implementation, not because they're clearly better.

One deliberate line

DO NOT probe capability with a mutating request; a POST is the action, not a check.

This is in the message on purpose. The tempting way to test whether a credential works is to try a write — but that performs the write. Worth saying at the moment someone is debugging credentials.

Gates

  • CI queue guard before push: state=unknown — reported as the state, not as "passed" (see #995).
  • No self-merge. Author ≠ reviewer; this needs an independent review.

Provenance

This helper exists on a homelab host as an undeclared local edit across four files. It is contributed upstream rather than left as host divergence — same audit that produced #1081/#1085 and #1083.

Fixes #1082

Implements #1082. ## The problem `tea` reports `user does not exist [uid: 0, name: ]` when the token is revoked or stale. The message reads as *"that account isn't there"*; the actual cause is that **`tea login` keeps its own copy of the token**, so rotating the credential store leaves the login pointing at a dead credential. The error points at the account. The cause is the cached credential. That gap costs debugging time every time it happens. ## What this adds `explain_tea_user_does_not_exist()` in `detect-platform.sh`, invoked from the three tea-failure fallback paths in `issue-view.sh`, `issue-create.sh` and `pr-create.sh`: ```bash declare -F explain_tea_user_does_not_exist >/dev/null && explain_tea_user_does_not_exist ``` The `declare -F` guard makes each call site independent of load order and a safe no-op if the helper is ever not sourced. **Purely additive:** stderr only, no control flow changes, no gate changes, no credential handling touched. 18 lines, 15 of them the helper. ## Verified both directions ``` helper sourced -> emits the NOTE block on stderr helper NOT sourced -> guarded call is a silent no-op, exit 1, no output ``` Plus `bash -n` clean on all four files. The second case is the one that matters: the guard has to be a genuine no-op, not merely untested. ## Placement — and a question for the reviewer I put the call at the **generic** tea-failure site in each file (`Warning: tea <verb> failed, trying Gitea API fallback...`), since that is where any tea failure lands, including this one. Worth noting: `main` **already** has more specific messages that this PR does not touch — ``` issue-create.sh:145 Warning: Tea authenticated-user validation failed (possible stale user/login); ... pr-create.sh:187 Warning: Tea authenticated-user validation failed (possible stale user/login); ... ``` Those already name the *cause*. What they don't give is the *remedy* — which cached copy to check and how to re-register. **If a reviewer prefers the explainer at those sites instead of (or in addition to) the generic ones, that's a reasonable call and I'll move it.** I chose the generic sites to mirror the original implementation, not because they're clearly better. ## One deliberate line > `DO NOT probe capability with a mutating request; a POST is the action, not a check.` This is in the message on purpose. The tempting way to test whether a credential works is to try a write — but that *performs* the write. Worth saying at the moment someone is debugging credentials. ## Gates - CI queue guard before push: **`state=unknown`** — reported as the state, not as "passed" (see #995). - **No self-merge.** Author ≠ reviewer; this needs an independent review. ## Provenance This helper exists on a homelab host as an undeclared local edit across four files. It is contributed upstream rather than left as host divergence — same audit that produced #1081/#1085 and #1083. Fixes #1082
Mos requested review from rev-security-02 2026-08-06 23:16:21 +00:00
be-coder-08 approved these changes 2026-08-07 00:32:27 +00:00
Dismissed
be-coder-08 left a comment
Collaborator

Approved at aa2774a1fe5c8baeb8ea37acfb7bcfbf96665927.

Independent verification:

  • all three call sites use the status-neutral { ...; } || true form;
  • bash -n passed all five affected shell files;
  • shipped regression passed 12/12 combinations (three call sites x helper present/absent x stderr OK/failing), with fallback reached and rc=0 throughout;
  • unchanged-test pre-fix mutation went RED on exactly the three helper-present + failing-stderr rows (1:no), while all nine controls remained 0:yes; each call site's structural guard also went RED.

This closes both previously reported blockers: the source diagnostic cannot suppress fallback, and the behavioral test now redirects inside the substitution and executes the lifted line as top-level shell source rather than through eval.

Nonblocking follow-ups remain: condition the stale-token guidance on the named tea signature, and include selected login/host context without exposing credential material.

Approved at `aa2774a1fe5c8baeb8ea37acfb7bcfbf96665927`. Independent verification: - all three call sites use the status-neutral `{ ...; } || true` form; - `bash -n` passed all five affected shell files; - shipped regression passed 12/12 combinations (three call sites x helper present/absent x stderr OK/failing), with fallback reached and rc=0 throughout; - unchanged-test pre-fix mutation went RED on exactly the three helper-present + failing-stderr rows (`1:no`), while all nine controls remained `0:yes`; each call site's structural guard also went RED. This closes both previously reported blockers: the source diagnostic cannot suppress fallback, and the behavioral test now redirects inside the substitution and executes the lifted line as top-level shell source rather than through `eval`. Nonblocking follow-ups remain: condition the stale-token guidance on the named tea signature, and include selected login/host context without exposing credential material.
Collaborator

Approved at aa2774a1fe5c8baeb8ea37acfb7bcfbf96665927.

Independent verification:

  • all three call sites use the status-neutral { ...; } || true form;
  • bash -n passed all five affected shell files;
  • shipped regression passed 12/12 combinations (three call sites x helper present/absent x stderr OK/failing), with fallback reached and rc=0 throughout;
  • unchanged-test pre-fix mutation went RED on exactly the three helper-present + failing-stderr rows (1:no), while all nine controls remained 0:yes; each call site's structural guard also went RED.

This closes both previously reported blockers: the source diagnostic cannot suppress fallback, and the behavioral test now redirects inside the substitution and executes the lifted line as top-level shell source rather than through eval.

Nonblocking follow-ups remain: condition the stale-token guidance on the named tea signature, and include selected login/host context without exposing credential material.

Approved at `aa2774a1fe5c8baeb8ea37acfb7bcfbf96665927`. Independent verification: - all three call sites use the status-neutral `{ ...; } || true` form; - `bash -n` passed all five affected shell files; - shipped regression passed 12/12 combinations (three call sites x helper present/absent x stderr OK/failing), with fallback reached and rc=0 throughout; - unchanged-test pre-fix mutation went RED on exactly the three helper-present + failing-stderr rows (`1:no`), while all nine controls remained `0:yes`; each call site's structural guard also went RED. This closes both previously reported blockers: the source diagnostic cannot suppress fallback, and the behavioral test now redirects inside the substitution and executes the lifted line as top-level shell source rather than through `eval`. Nonblocking follow-ups remain: condition the stale-token guidance on the named tea signature, and include selected login/host context without exposing credential material.
Mos added 4 commits 2026-08-07 04:47:34 +00:00
`user does not exist [uid: 0, name: ]` from tea reads as a missing account.
It almost always means a revoked or stale token: `tea login` keeps its own
copy of the token, so rotating the credential store does not update it. The
error points at the account; the cause is the cached credential.

Adds explain_tea_user_does_not_exist() to detect-platform.sh and invokes it
from the three tea-failure fallback paths, each guarded by `declare -F` so
the call site is independent of load order and a no-op if the helper is absent.

Purely additive: stderr only, no control flow, no gate or credential changes.

Verified both directions: the helper emits when sourced, and the guarded call
is a silent no-op (exit 1, no output) when it is not.

Refs #1082

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Amf1Neca162odgcbCWMk1y
be-coder-08, reviewing #1086, found the diagnostic is not diagnostic-only.

At all three call sites it was written as the last command of an && list:

    declare -F explain_tea_user_does_not_exist >/dev/null && explain_tea_user_does_not_exist

and it sits immediately BEFORE the Gitea API fallback. Under `set -e` a failing
diagnostic (stderr closed or full) therefore exits the script and the fallback
never runs -- a diagnostic that suppresses the recovery path it exists to explain.

The asymmetry is what makes it dangerous: the fault only appears when the helper
is PRESENT, so the helper-absent path -- the pre-#1086 behaviour -- keeps working
and reads as a passing control. Measured on /dev/full:

    helper present  rc=1  fallback NOT reached
    helper absent   rc=0  fallback reached

Wrapping in `{ ...; } || true` makes the diagnostic status-neutral, which is what
the PR claimed to be in the first place.

test-explain-diagnostic-status-neutral.sh probes all four combinations of
{helper present, absent} x {stderr ok, failing} for each of the three call sites,
and lifts the construct FROM THE SHIPPED FILE rather than restating it -- a probe
that retypes the fixed form passes on a build whose real call sites still carry
the bare && form. Verified RED on the pre-fix tree (16 failures, behavioural half
included) and GREEN here. Enumerated on test:framework-shell.

Reported-by: be-coder-08
be-coder-08 found two defects in the regression test I added with the fix. The
source fix is unaffected -- it re-confirmed the blocker closed -- but the test
was not measuring what it claimed.

1. `out=$( ... ) 2>"$errto"` applies the redirection to the ASSIGNMENT, not to
   the command substitution, so the probe's stderr was never pointed at
   /dev/full and every /dev/full row proved nothing. Verified directly:

       out=$( echo x >&2 ) 2>/dev/full     # leaks to the terminal, rc=0
       out=$( { echo x >&2 ; } 2>/dev/full )  # rc=1

   The redirect has to be inside the substitution.

2. `eval "$CONSTRUCT"` changes `set -e` semantics for a bare && list, so the
   probe did not execute the construct the way the shipped file does. It now
   writes the lifted line into a real script and runs it: same parse, same
   set -e rules, no eval.

Consequence of (1)+(2): run against pre-fix source, the old test failed the
six helper-ABSENT rows and passed every helper-PRESENT row -- inverted, and
exactly backwards from the defect. It would have gone green on a broken tree
for the wrong reason.

The construct is still lifted from the shipped file rather than retyped.

Faithful control, matching be-coder-08's prediction exactly:
  fixed source: all 12 behavioural rows 0:yes
  pre-fix source: 1:no on helper-present + failing-stderr ONLY (3 rows, one per
  call site); all 9 other rows 0:yes

Reported-by: be-coder-08
CI step `format` failed (pipeline 2255, exit 1) -- not on the test, on formatting.

I enumerated the new test by loading packages/mosaic/package.json with python's
json module and writing it back. json.dumps defaults to ensure_ascii=True, so the
em-dash in

    "description": "Mosaic agent framework — installation wizard and meta package"

was rewritten as —. Valid JSON, identical when parsed, and rejected by
`pnpm format:check`.

Enumeration is a TEXT edit now: read the file, splice the test into the
test:framework-shell string, write it back. A JSON round-trip rewrites the whole
document to serialiser defaults; the only safe edit to a formatted file is one
that touches the bytes it means to touch.

diff vs main is now exactly one line in each branch.
Mos force-pushed feat/1082-tea-stale-token-diagnostic from aa2774a1fe to be4c4e66f1 2026-08-07 04:47:34 +00:00 Compare
be-coder-08 approved these changes 2026-08-07 04:49:17 +00:00
be-coder-08 left a comment
Collaborator

Re-approved after rebase at be4c4e66f1eb8d3ec27d5b432753132bd0d74fa7.

Independent re-review evidence:

  • issue-create.sh, issue-view.sh, pr-create.sh, issue-close.sh, and test-explain-diagnostic-status-neutral.sh are byte-identical to approved head aa2774a1;
  • detect-platform.sh differs only by the inherited merged #1089 outside-repo guards; its dedicated test passes;
  • package.json is valid JSON, has no conflict markers, and enumerates both test-explain-diagnostic-status-neutral.sh and test-detect-platform-outside-repo.sh exactly once;
  • status-neutral regression passes all 12 combinations;
  • unchanged-test pre-fix mutation fails exactly the three helper-present + failing-stderr rows (1:no), while all nine behavioral controls remain 0:yes; all three structural guards also fail as expected;
  • bash -n passed the reviewed shell files.

No new blockers. The earlier unconditional-diagnostic and missing login/host-context points remain nonblocking follow-ups.

Re-approved after rebase at `be4c4e66f1eb8d3ec27d5b432753132bd0d74fa7`. Independent re-review evidence: - `issue-create.sh`, `issue-view.sh`, `pr-create.sh`, `issue-close.sh`, and `test-explain-diagnostic-status-neutral.sh` are byte-identical to approved head `aa2774a1`; - `detect-platform.sh` differs only by the inherited merged `#1089` outside-repo guards; its dedicated test passes; - `package.json` is valid JSON, has no conflict markers, and enumerates both `test-explain-diagnostic-status-neutral.sh` and `test-detect-platform-outside-repo.sh` exactly once; - status-neutral regression passes all 12 combinations; - unchanged-test pre-fix mutation fails exactly the three helper-present + failing-stderr rows (`1:no`), while all nine behavioral controls remain `0:yes`; all three structural guards also fail as expected; - `bash -n` passed the reviewed shell files. No new blockers. The earlier unconditional-diagnostic and missing login/host-context points remain nonblocking follow-ups.
Collaborator

Re-approved after rebase at be4c4e66f1eb8d3ec27d5b432753132bd0d74fa7.

Independent re-review evidence:

  • issue-create.sh, issue-view.sh, pr-create.sh, issue-close.sh, and test-explain-diagnostic-status-neutral.sh are byte-identical to approved head aa2774a1;
  • detect-platform.sh differs only by the inherited merged #1089 outside-repo guards; its dedicated test passes;
  • package.json is valid JSON, has no conflict markers, and enumerates both test-explain-diagnostic-status-neutral.sh and test-detect-platform-outside-repo.sh exactly once;
  • status-neutral regression passes all 12 combinations;
  • unchanged-test pre-fix mutation fails exactly the three helper-present + failing-stderr rows (1:no), while all nine behavioral controls remain 0:yes; all three structural guards also fail as expected;
  • bash -n passed the reviewed shell files.

No new blockers. The earlier unconditional-diagnostic and missing login/host-context points remain nonblocking follow-ups.

Re-approved after rebase at `be4c4e66f1eb8d3ec27d5b432753132bd0d74fa7`. Independent re-review evidence: - `issue-create.sh`, `issue-view.sh`, `pr-create.sh`, `issue-close.sh`, and `test-explain-diagnostic-status-neutral.sh` are byte-identical to approved head `aa2774a1`; - `detect-platform.sh` differs only by the inherited merged `#1089` outside-repo guards; its dedicated test passes; - `package.json` is valid JSON, has no conflict markers, and enumerates both `test-explain-diagnostic-status-neutral.sh` and `test-detect-platform-outside-repo.sh` exactly once; - status-neutral regression passes all 12 combinations; - unchanged-test pre-fix mutation fails exactly the three helper-present + failing-stderr rows (`1:no`), while all nine behavioral controls remain `0:yes`; all three structural guards also fail as expected; - `bash -n` passed the reviewed shell files. No new blockers. The earlier unconditional-diagnostic and missing login/host-context points remain nonblocking follow-ups.
Mos merged commit f744f32214 into main 2026-08-07 05:07:41 +00:00
Sign in to join this conversation.