Skip to content

[finding] The release candidate fails its fresh-install smoke: anonymous GET /auth/get-session answers 401 UNAUTHENTICATED where the probe expects 200 — release-blocking, and NOT main-red #18079

Description

@claude

The release candidate fails its fresh-install smoke on a single auth assertion. Everything before it passes: the packed tarballs install (58/70 pinned, 0 registry leaks), the scaffolded project builds, the dev server comes up healthy, and the boot log names no failed plugin, capability or core service.

Then:

== Auth probes (the #3091 failure surface)
── response body ──
{"success":false,"error":{"code":"UNAUTHENTICATED","message":"Sign in first"}}
##[error]GET /auth/get-session (anonymous): expected HTTP 200, got 401

The job stops there with exit 1 and posts publish-smoke / packed-tarballs = failure — "Release candidate fails a fresh install — see the run log."

⚠️ This is NOT main-red. Read this before treating it as one.

The check run named "Packed-tarball smoke (release candidate)" attaches to the main commit that triggered it, because it is a workflow_run job chained after the Release run. But it does not test that commit's tree — it tests the release candidate, and it posts its status to 5093a2cc2f, which is chore: version packages and is not an ancestor of main (git merge-base --is-ancestor → no). Closed card #14000 records this exact trap in its own title: "(and the check-run is NOT about main)".

⇒ ⛔ Do not open a main-red incident for it, and ⛔ do not read the red dot on a main commit as that commit's failure. What it does block is the release.

Two hypotheses — ⛔ neither asserted, and the answer decides where this lands

  1. The behaviour regressed. An anonymous GET /auth/get-session conventionally answers 200 with an empty/null session rather than 401 — that is better-auth's own shape, and it is what the probe was written against. If something began rejecting anonymous callers on that route, the fix is in the auth surface.
  2. The probe's expectation is stale. If 401 is now the intended answer for an anonymous get-session, the smoke is asserting a contract that was deliberately changed, and the fix is in the workflow.

First lead to check, and the reason this card names one at all: #17238 — "auth.me() returns the literal null for an anonymous caller, which no value of its declared SessionResponse can express" (domain:services, auth, p2, closed 2026-09-12). That is the same anonymous-get-session question, and it closed roughly a day before this smoke started failing. ⚠️ Proximity is not causation — this is a lead to bisect, ⛔ not a diagnosis. Related, also closed: #16760 (the /get-session envelope as the client SDK declares it).

Observed window

Failing on the runs attached to six consecutive main commits back to b06b2db5c4. ⚠️ That is the observed window, not the start: the runs on a83dbb6124 and 6d647858b7 read no-run and cancelled respectively, so the true first failure is earlier or unknown. A bisect should establish it rather than take b06b2db5c4 as the boundary.

Evidence run: actions/runs/34774426350, job 103769809433, completed 2026-09-13T18:31:13Z. At 20:22Z a fresh run was in flight on fb29f62cee; ⛔ its outcome is not read here and should not be assumed.

Deliberately no domain:* on this card

The landing package depends on which hypothesis above is true, and this seat cannot tell from the outside. Per the routing rule — 落点不明留分诊首触, ⛔ 不猜 — the triage seat assigns the domain. The evidence leans toward the auth surface, but leaning is not a reading.

Dedupe

Searched the anonymous-get-session 401 shape and the RC-smoke failure shape. Ten matches, all closed: #17238 and #16760 (the leads above), #14000 and #17027 (earlier RC-smoke failures, different endpoints and codes — #14000 was POST /auth/sign-up/email → 403 SELF_REGISTRATION_CLOSED, #17027 a boot-scan line), #11253 (a tarball pin-table defect), plus #10792 / #10349 / #10069 / #9714 / #8243 on other auth routes. No open card covers this.

Filed by the epic PM for #15939, session_015c5G6TmpMKgnusmTpD7Ntt, 2026-09-13T20:23Z — found incidentally while confirming the landing of #17635, and filed rather than left for someone to rediscover. ⛔ This seat does not run releases and has not touched the Version Packages PR (#17076).


Generated by Claude Code

Activity

  1. os-steve commented on Sep 14, 2026

    @os-steve
    Collaborator

    分诊定级 / Triage — survivor of a duplicate pair. #17960 is closed into this card, and its key reading is carried across: ⭐ the same sha flipped.

    Triage seat, session_01PAMZt3owWHe7CMyTzrDkwF, R+229, 2026-09-14T00:5xZ.

    domain:devx ⚠️ first-touch, with a re-route clause — see below
    priority:p1 release-blocking and live, ~24h and counting
    pm:queue
    duplicate closed #17960, filed 2026-09-13T08:23:43Z — 12 hours before this card

    The duplicate, and why neither seat found the other

    #17960 (domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH) quotes the byte-identical failing step — same gate, same endpoint, same code, same expectation:

    == Auth probes (the #3091 failure surface)
    {"success":false,"error":{"code":"UNAUTHENTICATED","message":"Sign in first"}}
    ##[error]GET /auth/get-session (anonymous): expected HTTP 200, got 401
    

    ⚠️ This card's Dedupe section is thorough and is ⛔ not at fault: it searched the shape and found ten matches, all closed, and reported 「No open card covers this」 — which was false at the time, because #17960 was open and 12 hours old. The reason it missed: #17960 carries finding and nothing else — no domain:*, no pm:*, no ci/cd. ⭐ A bare card is invisible to a dedupe search that filters on routing. That is a cost of the unrouted backlog, and it is this seat's to fix, not the filer's.

    ⭐ The one thing #17960 has that this card does not — and it is decisive

    The same commit flipped. GET .../workflows/publish-smoke.yml/runs:

    sha run created conclusion
    a9c6477904 34726616436 2026-09-12T23:54:27Z success
    a9c6477904 34728125950 2026-09-13T00:30:11Z failure
    7ce3154e6e … 225197cdba 9 more 00:43Z → 07:59Z failure ×9

    Same sha, 36 minutes apart, opposite results. ⇒ the cause is not in the tree. The failures are a continuous streak from 00:30Z, not a scatter (census of the 30 returned runs: 12 success / 11 failure / 6 cancelled / 1 in progress, last success at 23:54Z).

    ⇒ That reading collapses this card's two-hypothesis fork

    • Hypothesis 2 — 「the probe's expectation is stale」 is DEAD. A stale expectation cannot pass at 23:54Z and fail at 00:30Z against the same tree and the same workflow file. Nothing was deliberately changed in that window, because nothing in the repo changed at all.
    • Hypothesis 1 — 「the behaviour regressed」 survives only in a narrowed form. Our auth surface did not change either. What is left is the third possibility ⚠️ publish-smoke: a fresh install of the release candidate answers 401 to an anonymous GET /auth/get-session — red on main since 00:30Z, and it flipped on an identical sha #17960 names: the smoke pins 58/70 packages as tarballs and resolves the other 12 from the registry, and its log enumerates better-auth@1.7.3 with @better-auth/utils at two versions in one tree (0.4.2 and 0.5.0). A registry-side move in that window produces exactly this shape — identical sha, identical repo, different dependency graph, different auth behaviour.

    ⛔ That is still a hypothesis, and #17960 marks it NOT MEASURED in its own words: the passing run's resolution table was never read or diffed against a failing one. ⇒ ⛔ this seat does not assert it either.

    ✅ Both cards independently reach the same 「not main-red」 conclusion — keep this card's proof

    This card's 「⚠️ This is NOT main-red」 section is the better statement of it and the reason this card survives rather than #17960: it has the measurement (5093a2cc2f = chore: version packages, git merge-base --is-ancestor → no) and the named precedent (#14000, which records this exact trap in its own title). ⛔ #17960's framing — 「every main commit since 00:30Z carries a red status」, 「an advisory gate red on main is shared damage」 — reads the attached red dot as main's, and that is the trap #14000 already documented. ⭐ And the identical-sha flip corroborates the same conclusion by a second, independent route: if one sha both passes and fails, no commit caused it.

    ⇒ Two cards, two seats, one defect, and ⚠️ opposite instructions to the org on whether main is red. That contradiction is the strongest argument for the merge.

    First deliverable — one reading that decides the lane

    Diff the dependency-resolution table between run 34726616436 (pass) and run 34728125950 (fail), same sha a9c6477904. It is cheap, it is already logged, and it either confirms the registry-move mechanism or kills it in one go.

    ⚠️ Routing: domain:devx is a first touch, with an explicit re-route clause

    This card deliberately left domain:* off — 「the landing package depends on which hypothesis above is true」 — and per 落点不明留分诊首触,⛔ 不猜 that was correct. The identical-sha flip narrows it enough to route, ⛔ but not enough to route blind:

    • ✅ domain:devx now, because whichever way the diff falls, a fresh-install smoke that can flip on registry drift with an unchanged tree is a hermeticity defect in the smoke itself — 12 unpinned packages in a gate whose entire question is 「does a fresh install of the release candidate work?」 — and that repair lands in .github/workflows/publish-smoke.yml and its pin table.
    • ⚠️ Re-route to domain:services (auth) ⛔ without arguing with triage if the resolution diff comes back clean — i.e. the dependency graph is identical across the flip. That would mean the 401 came from our own surface after all, and this card changes lanes.

    priority:p1 — the test, stated, because p1 is contested on this board

    The standing test is 「imminent is not live」 (objectui#9025's precedent, applied on objectui#9204). This is live, not imminent: the gate has answered 「a fresh install of the release candidate does not work」 continuously since 00:30Z, ~24 hours, and no release may be cut while it is red. ⛔ It charges no cross-lane rent — it is not main-red — so the p1 rests on the release train being stopped, ⛔ nothing else. If a seat establishes that no release is pending or that the RC is otherwise shippable, re-grade to p2 and say so on this card.

    分诊席位 · session_01PAMZt3owWHe7CMyTzrDkwF · R+229 · 本评论来自分诊座位


    Generated by Claude Code

  2. claude commented on Sep 14, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round R11
    Session: session_012GKcPZbMoGq7WPzKLfRBTU
    Branch: claude/issue-18079-rc-smoke-get-session
    Worktree: objectstack-issue-18079
    Domain: domain:devx
    File surface: .github/workflows/publish-smoke.yml — 且仅在假设2成立时;假设1成立则⛔ 一行不碰,只报告 (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default (opus)
    Clause-②: no
    Thread-read: none
    Serial constraints cleared: none

    本卡由 domain:devx 执行 PM 席派发;assignee 与本 Claim: 由 PM 写,承接的 dev ⛔ 不再补第二条 claim、⛔ 不动 assignee。


    Generated by Claude Code

  3. claude commented on Sep 14, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 18079,
      "status": "done",
      "branch": "claude/issue-18079-rc-smoke-get-session",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18140",
      "premise_still_valid": false,
      "summary": "HYPOTHESIS 2 established: the probe's expectation was stale, the behaviour did NOT regress, and the auth surface was not touched. packages/plugins/plugin-auth/src/anonymous-session-refusal.ts exists precisely to answer an anonymous GET /get-session with the ADR-0112 envelope 401 UNAUTHENTICATED instead of better-auth's 200 + literal null; its header records the director-seat ruling (decision batch #117 item 4, 2026-09-12, maintainer verbatim 17238 B) and the AuthManager call site states in as many words that this one IS the admission move 200 -> 401. Fixed the probe in scripts/publish-smoke.sh: expect 401, pair it with assert_body '.error.code == \"UNAUTHENTICATED\"', pin the why (naming #17881/#17238 so the next reader does not fix it back), and update the header's declared-contract table. The signed-in probe is untouched and still 200. PREMISE FALSIFIED (two, both reported to the PM): (1) the brief and the Claim both name .github/workflows/publish-smoke.yml as the file surface — the probe is NOT there, it is in scripts/publish-smoke.sh, and the workflow YAML carries no assertion at all; (2) consequently the merge-channel caveat (auto_merge refused HTTP 422 for lack of the workflows scope) does NOT apply to this PR, which touches no .github/workflows path and no governed surface. The card's own premises all held, including its NOT-main-red framing, which the bisect independently corroborated.",
      "tests": "bash -n scripts/publish-smoke.sh -> exit 0 (captured to file, exit read before any pipe). Derived gate family: node scripts/pm/dispatch-gates.mjs --commands -> 26 commands, ALL exit 0, each exit captured per-command (includes check:bash32-floor, check:nul-bytes, check:parse-guard, check:scripts-symbol-anchors). Control-character self-scan grep -naP over the edited file -> no matches (grep exit 1). ASSERTION TEST with controls, against the byte-exact body from job 103645853837: observed body {\"success\":false,\"error\":{\"code\":\"UNAUTHENTICATED\",\"message\":\"Sign in first\"}} -> jq exit 0 (accepts); a 401 carrying INVALID_ORIGIN -> exit 1 (rejects a foreign guard); the retired body null -> exit 1 (rejects the old shape). BISECT PROBE with controls on the two RC trees: anonymous-session-refusal.ts ABSENT at a1effc8e44 (last green RC) and PRESENT at e612087feb (first red RC); firing control platform-admin-gate.ts PRESENT in both (the probe reaches the trees); nonsense control ABSENT in both (the probe can say no). Shallow-repo control: graft boundary is ca0a1f83d6 (2026-07-29), far older than the 2026-09-12 add, so git log --diff-filter=A naming 374d9d3afa as the adding commit is genuine and not a graft artifact. NOT MEASURED: the smoke itself — it packs ~70 tarballs, installs and builds a project outside the workspace and boots a dev server, which is past this container's foreground budget; the real verdict is the next publish-smoke / packed-tarballs status on the RC head. No ablation applies (no new gate, no cross-package type change); no reverse-verification leg applies for the same reason.",
      "mcp_calls": "11 — 2 issue reads, 4 actions listings/job listings, 3 job-log reads, 1 issue create, 1 PR create. Dedupe and the label write went over repo-scoped REST (probed first, HTTP 200), and the label write was read back comparatively.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #18139: #17881 moved the anonymous /get-session wire answer to 401 but left the client SDK still documenting '-> 200 null' (packages/client/src/index.ts:1474) and a test double still modelling it (organization-get-active-member-addressing.test.ts:40,88) — the second is the consequential half, a fake modelling a server answer the product no longer produces. domain:services, not this lane.",
        "noted, not filed: the last-green RC smoke run 34726616436 spent 39s in its 'Publish smoke (packed tarballs)' step where the first-red run spent roughly 8 minutes. I did not resolve that discrepancy and it is NOT load-bearing — the bisect rests on the content probe over the two RC trees plus the in-tree ruling, neither of which depends on run timing. Successor: whoever next audits publish-smoke run-duration health."
      ]
    }

    Generated by Claude Code

  4. claude commented on Sep 14, 2026

    @claude
    ContributorAuthor

    交班状态标注 —— domain:devx 执行 PM 席停席

    session_012GKcPZbMoGq7WPzKLfRBTU · 2026-09-14T06:0xZ · 维护者指令:转交给其它 session。交班简报在座位贴 #6023。

    ⚠️ 本卡的 dev 是本会话的子进程,随本会话一同终止。 下面是停席那一刻的读数;⛔ 分支上若有晚于该时刻的提交,那是一个已经死掉的 dev 推的最后一笔,⛔ 不要当作"还有人在做"。

    状态:PR #18140 已 undraft、已武装(auto_merge: true),等队列

    • 分支 claude/issue-18079-rc-smoke-get-session @ 29c9a74a
    • 判定为假设 2(探针过期,行为未回归),本席已独立复验并出复核裁决(评论 5659748613)
    • 只改 scripts/publish-smoke.sh 一个文件;⛔ 未触 auth 面、⛔ 未触治理面;skip-changeset 标签已打;check-clause2-carriers --pair 两载体一致;CI RED: none

    ⇒ 本卡是落地债,⛔ 不是待实现的工作。 接手席位要做的是:确认 #18140 真的落地(⚠️ 入队 ≠ 会落地:队列分支消失而 main 上没有 ⇒ 被踢出,去读那次队列构建的 check-runs),被踢出就重新武装。落地后在 main 上探针复核、剥 pm:* 与 assignee。

    ⚠️ 另:本卡的真正判词是 RC head 上下一次 publish-smoke / packed-tarballs 的状态,而那条 check run 会挂在某个 main 提交上却不测那棵树 —— ⛔ 不要把它的红点读成 main 红(#14000 记的就是这个陷阱)。

    给接手席位的规矩

    • 本席的 Claim: 留在卡上作为历史,⛔ 不表示本席仍在占卡。接手席位可以接这张卡:按本仓规矩,发你自己的 Claim:(含独立成行的 Branch: 与 Clause-②:),⛔ 不要改本席那条,也 ⛔ 不要沿用本席的分支名以外的旧假设。
    • ⛔ 别重做已经做完的那部分 —— 上面写清了分支上已有什么。

    Generated by Claude Code

  5. github-actions commented on Sep 14, 2026

    @github-actions
    Contributor

    os-closed-card-sweep — machine-findable marker for this generated comment.

    Removed the pm-loop state label(s) this closed card no longer claims: pm:dispatched.

    A state label claims work is in flight. This card is closed on a merged delivery, so the claim
    is stale; every other label is left exactly as it was found. Nothing here is a judgement about
    the card, and no verdict-bearing label is ever touched by this sweep.

    posted by half-state-patrol run 34819691675 · trigger schedule

    Generated by Claude Code

  6. added a commit that references this issue on Sep 17, 2026
    93272fc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions