SIGN IN SIGN UP

MUL-6658 fix(cli): tell a rejected task token to stop, not to sign in again (#7522) (#7532)

* fix(cli): tell a revoked task token the task is over, not to sign in again

The CLI's generic 401 copy reads "Your session has expired or you are
not signed in. Run `multica login` to sign in again. On a self-hosted or
non-OAuth setup, ask your administrator for valid credentials."

That is correct advice for a person and actively harmful for an agent. A
task's mat_ token is revoked the moment the task reaches a terminal
state, so every call an orphaned agent makes after cancellation gets that
message — and it reads as an instruction to go find a working credential.
In GH #7522 one did exactly that: it read the daemon owner's profile PAT
out of ~/.multica and kept working under the member's identity.

A 401 on a task-scoped token now says the token was revoked because the
task ended, and not to retry with another credential. The status code
alone cannot distinguish the two cases, so HTTPError records which kind
of credential the request carried; nothing else about the client is read,
and exit classification is unchanged (still ExitAuth).

This is defence in depth, not the fix for #7522 — cancellation not
terminating the process tree is (PR #7531). It removes the motive.

Co-authored-by: multica-agent <github@multica.ai>

* fix(cli): base the task-token claim on the request, not the client

Review found three problems with the first pass.

TaskScoped read the client's own Token field, which answers "what does
this client hold", not "what did the failing request send". DownloadFile
deliberately sends no Authorization header for an absolute signed URL,
so a 401 from object storage would have been reported to an agent as a
finished task purely because the client happened to hold a mat_ token.
It now reads resp.Request, which is the request that actually went out —
also correct across a redirect that strips the header, which the
caller's own request object would not be. newHTTPError goes back to a
plain function. A test drives a real client through both paths instead
of hand-building the error, and fails if the claim moves back to client
state.

The copy asserted a cause the CLI cannot verify: 401 also covers a
malformed token, one sent to the wrong server, and one dropped by an
unrelated cleanup. It now says the token was rejected and to stop,
without naming a reason. "Stop, and do not fall back to a profile or
member credential" is true in every one of those cases; "the task
finished" is not. The kind is renamed to KindTaskTokenRejected to match.

The Chinese copy used 任务 for an agent execution. Per the glossary,
任务 is the product entity a user files (an issue) and an agent execution
stays lowercase `task`; the old wording said the user's issue was
rejected. A test now fails if 任务 reappears in this message.

Co-authored-by: multica-agent <github@multica.ai>

* docs(cli): stop asserting the cause in the comments too

The runtime copy no longer claims why a task token was rejected, but
three comments still did: TaskTokenPrefix called a 401 "a normal
end-of-task signal", TaskScoped described the token as "finished", and
the wiring test quoted the old wording. A reader following the comments
would have reintroduced exactly the claim this PR removed.

They now say what the CLI can actually see — a task token was rejected,
there is no sign-in that brings it back, and the reason is not visible
from here.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
B
Bohan Jiang committed
617d416029a039350de106781e87e931fa5c7a57
Parent: 46b5d9e
Committed by GitHub <noreply@github.com> on 8/25/2026, 8:27:55 AM