You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-auth: the Magic Link mail says the link expires in 10 minutes, while the link it sends lives 300 seconds (measured) #25112
Filing gate ①, user-visible text that is false (measured). Filed by the domain:services PM seat, seat 1 (seat post #6021, session_011TdZ8GjixNZFLz1xJD9zzK). The source is #25096's dev report (6315144841, out-of-scope finding 2). ⛔ Not a claim. Unlabelled and ungraded, for triage.
Reader, and what they do: triage grades and routes this card. It reads as plugin-auth and plugin-email, the domain:services lane.
What was measured
POST /api/v1/auth/sign-in/magic-link sends the auth.magic_link mail with expiresInMinutes: 10, and its copy says "This link expires in 10 minutes". The verification row it stores lives 300 s, because magicLink() is mounted without expiresIn and the vendor default is 300 s. The lift does not change it (same on 1.7.3).
Where it lives (origin/mainba244f1b48)
packages/plugins/plugin-auth/src/auth-manager.ts: sendMagicLink passes expiresInMinutes: 10, and magicLink() is mounted with no expiresIn.
packages/plugins/plugin-email/src/templates/auth-templates.ts: the magic_link copy.
Governing rule
Declared means enforced: the lifetime the mail states is the lifetime the link has, from one source. Which value wins is for whoever takes the card to decide. Magic Link is opt-in (pluginConfig.magicLink ?? false).
Claim: PM loop round 1 · 2026-10-22T13:08Z
Session: session_016g7nYP9wdNx1GDa5XDr7Uy (the dispatching seat; the dev is a mode:subagent run of this session, told apart by its branch)
Account: os-sales (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-25112-magic-link-ttl
Worktree: objectstack-issue-25112
Domain: domain:services
Seat: domain:services#1 (seat post #6021)
File surface: packages/plugins/plugin-auth/src/** (the magicLink() mount and sendMagicLink), packages/plugins/plugin-email/src/templates/** and its bundles (the magic_link copy), their tests, .changeset/ (stop on breach; explain in the report)
Container & model: S 级机械卡 (the seat's ruling below removes the design choice), mode:subagent, model: opus (the default tier; dispatch-gates --tier --repo objectstack-ai/objectstack reads no path-derived mandate; one step above the floor because the surface is an auth flow). The dev runs as the general-purpose agent with .claude/agents/os-dev.md as its role file: os-dev is not in this session's agent roster (seat post §1).
Clause-②: no
Responsibility: plugin-auth's magic-link mount and send path | none — the vendor default is the only lifetime source today | every recipient of a Magic Link mail on a deployment that opts in (pluginConfig.magicLink); reached today wherever it is enabled
Thread-read: none
Serial constraints cleared: none — no open PR touches auth-manager.ts or plugin-email's auth templates (all 10 open PRs' file lists read at claim time). The better-auth family hot-file row (seat post §3) is free since PR #25109.
Seat ruling (the card leaves the value to the claiming seat): the link keeps its measured lifetime, 300 s. The mount states it explicitly from ONE constant, and the mail's expiresInMinutes is derived from that same constant, so the copy says what the link does. ⛔ No longer lifetime: lengthening a sign-in credential's validity is a security-boundary relaxation and stays with the maintainer.
{
"issue": 25112,
"status": "done",
"branch": "claude/issue-25112-magic-link-ttl",
"pr": "https://github.com/objectstack-ai/objectstack/pull/25238",
"session": "session_016g7nYP9wdNx1GDa5XDr7Uy (subagent of the dispatching seat; head 1459742566eb4d1b2271aa01d4c622f231ab345b)",
"premise_still_valid": true,
"summary": "Premise re-measured on origin/main 123ff6f4e: sendMagicLink passed a hard-coded expiresInMinutes: 10 and magicLink() was mounted with no expiresIn, so the installed better-auth 1.7.12 default applied (dist/plugins/magic-link/index.mjs: opts.expiresIn || 300). The effective lifetime is 300 s, which matches the seat ruling, so no needs_decision. Fix, producer side only: one local constant magicLinkExpiresInSec = 5 * 60 beside the mount, passed as expiresIn, and expiresInMinutes = Math.round(magicLinkExpiresInSec / 60). Every locale now says 5 minutes, and the link's lifetime is unchanged. plugin-email does not move: all 4 locale rows of auth.magic_link render only {{expiresInMinutes}}, nothing hard-coded. One patch changeset for @objectstack/plugin-auth, .changeset/25112-magic-link-ttl.md. Card assignee is os-sales; I did not write it.",
"tests": "head 1459742566. plugin-auth build + typecheck + test under the lock: VERDICT command-exit 0, Test Files 185 passed (185), Tests 3487 passed | 10 skipped (3497). Targeted test src/auth-email-locale.test.ts: 69/69. Pins: the existing payload pin moves from 10 to 5; a new pin checks that the mount expiresIn === 300 and that expiresInMinutes*60 === the mount's expiresIn. Ablations, fix committed first, both through scripts/ablation-replace.mjs (anchor x1 -> x0, blob c3dd34d7bf34 -> mutated -> restored c3dd34d7bf34 == HEAD blob, git status clean). (1) expiresInMinutes put back to the literal 10: 2 failed | 67 passed ('expected 10 to be 5', 'expected 600 to be 300'). (2) expiresIn deleted from the mount: 1 failed | 68 passed ('expected undefined to be 300'). No dist rebuild leg was needed: the test imports ./auth-manager from src.",
"gates": "Local: dispatch-gates --commands --repo objectstack-ai/objectstack derived 70 commands for the 3-path diff (+36/-2), run under os-verify-lock (VERDICT command-exit 0). On the first pass 68/70 exited 0 and 2 exited 3 (PREREQUISITE NOT MET): check-plugin-teardown-shape --self-test on a shallow clone, and check:dual-build-cjs-loads with no full dist. I ran git fetch --unshallow origin main and pnpm build --concurrency=2 (76/76, 75 cached), then re-ran both: exit 0 each. The --ran reconciliation exited 0: '70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN'. CI on PR #25238 at head 1459742566: in_progress when this report was written (Build Core, Test Core 1-6, Lint, Type Check, Governed Surface Queue Guard, Check Changeset all queued or in_progress; two opt-in jobs skipped). main moved 2 commits after base 123ff6f4e, neither touching plugin-auth or plugin-email, and merge-tree is clean, so no merge was needed.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm (Clause-②: no)",
"mcp_calls": "0",
"api_writes": "2 relay strokes: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /issues/25238/assignees [os-sales], body read back 3251/3251 bytes identical; (2) comment = POST /repos/objectstack-ai/objectstack/issues/25112/comments (this report). Plus 1 git push of the branch (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none — noted in the PR's Acceptance notes only: the plugin-email template tests render auth.magic_link with the sample value expiresInMinutes: 10. It is render data that claims no lifetime, so it is not a defect."
],
"line_budget": "38 changed lines (+36/-2) across 3 files",
"files_changed": [
".changeset/25112-magic-link-ttl.md",
"packages/plugins/plugin-auth/src/auth-email-locale.test.ts",
"packages/plugins/plugin-auth/src/auth-manager.ts"
],
"deviations": "Both re-runs (git fetch --unshallow and the full pnpm build) are disclosed in gates. The plugin-email template files did not need changing, which the order's file surface allowed for, so only plugin-auth carries a changeset. integration/test:repo layers are left to CI."
}
Activity
objectstack-fleet commentedon Oct 22, 2026
Claim: PM loop round 1 · 2026-10-22T13:08Z
Session:
session_016g7nYP9wdNx1GDa5XDr7Uy(the dispatching seat; the dev is amode:subagentrun of this session, told apart by its branch)Account:
os-sales(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-25112-magic-link-ttlWorktree:
objectstack-issue-25112Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface:
packages/plugins/plugin-auth/src/**(themagicLink()mount andsendMagicLink),packages/plugins/plugin-email/src/templates/**and its bundles (themagic_linkcopy), their tests,.changeset/(stop on breach; explain in the report)Container & model:
S 级机械卡(the seat's ruling below removes the design choice),mode:subagent,model: opus(the default tier;dispatch-gates --tier --repo objectstack-ai/objectstackreads no path-derived mandate; one step above the floor because the surface is an auth flow). The dev runs as the general-purpose agent with.claude/agents/os-dev.mdas its role file:os-devis not in this session's agent roster (seat post §1).Clause-②: no
Responsibility:
plugin-auth's magic-link mount and send path|none — the vendor default is the only lifetime source today|every recipient of a Magic Link mail on a deployment that opts in (pluginConfig.magicLink); reached today wherever it is enabledThread-read: none
Serial constraints cleared: none — no open PR touches
auth-manager.tsorplugin-email's auth templates (all 10 open PRs' file lists read at claim time). The better-auth family hot-file row (seat post §3) is free since PR #25109.Seat ruling (the card leaves the value to the claiming seat): the link keeps its measured lifetime, 300 s. The mount states it explicitly from ONE constant, and the mail's
expiresInMinutesis derived from that same constant, so the copy says what the link does. ⛔ No longer lifetime: lengthening a sign-in credential's validity is a security-boundary relaxation and stays with the maintainer.objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{ "issue": 25112, "status": "done", "branch": "claude/issue-25112-magic-link-ttl", "pr": "https://github.com/objectstack-ai/objectstack/pull/25238", "session": "session_016g7nYP9wdNx1GDa5XDr7Uy (subagent of the dispatching seat; head 1459742566eb4d1b2271aa01d4c622f231ab345b)", "premise_still_valid": true, "summary": "Premise re-measured on origin/main 123ff6f4e: sendMagicLink passed a hard-coded expiresInMinutes: 10 and magicLink() was mounted with no expiresIn, so the installed better-auth 1.7.12 default applied (dist/plugins/magic-link/index.mjs: opts.expiresIn || 300). The effective lifetime is 300 s, which matches the seat ruling, so no needs_decision. Fix, producer side only: one local constant magicLinkExpiresInSec = 5 * 60 beside the mount, passed as expiresIn, and expiresInMinutes = Math.round(magicLinkExpiresInSec / 60). Every locale now says 5 minutes, and the link's lifetime is unchanged. plugin-email does not move: all 4 locale rows of auth.magic_link render only {{expiresInMinutes}}, nothing hard-coded. One patch changeset for @objectstack/plugin-auth, .changeset/25112-magic-link-ttl.md. Card assignee is os-sales; I did not write it.", "tests": "head 1459742566. plugin-auth build + typecheck + test under the lock: VERDICT command-exit 0, Test Files 185 passed (185), Tests 3487 passed | 10 skipped (3497). Targeted test src/auth-email-locale.test.ts: 69/69. Pins: the existing payload pin moves from 10 to 5; a new pin checks that the mount expiresIn === 300 and that expiresInMinutes*60 === the mount's expiresIn. Ablations, fix committed first, both through scripts/ablation-replace.mjs (anchor x1 -> x0, blob c3dd34d7bf34 -> mutated -> restored c3dd34d7bf34 == HEAD blob, git status clean). (1) expiresInMinutes put back to the literal 10: 2 failed | 67 passed ('expected 10 to be 5', 'expected 600 to be 300'). (2) expiresIn deleted from the mount: 1 failed | 68 passed ('expected undefined to be 300'). No dist rebuild leg was needed: the test imports ./auth-manager from src.", "gates": "Local: dispatch-gates --commands --repo objectstack-ai/objectstack derived 70 commands for the 3-path diff (+36/-2), run under os-verify-lock (VERDICT command-exit 0). On the first pass 68/70 exited 0 and 2 exited 3 (PREREQUISITE NOT MET): check-plugin-teardown-shape --self-test on a shallow clone, and check:dual-build-cjs-loads with no full dist. I ran git fetch --unshallow origin main and pnpm build --concurrency=2 (76/76, 75 cached), then re-ran both: exit 0 each. The --ran reconciliation exited 0: '70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN'. CI on PR #25238 at head 1459742566: in_progress when this report was written (Build Core, Test Core 1-6, Lint, Type Check, Governed Surface Queue Guard, Check Changeset all queued or in_progress; two opt-in jobs skipped). main moved 2 commits after base 123ff6f4e, neither touching plugin-auth or plugin-email, and merge-tree is clean, so no merge was needed.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm (Clause-②: no)", "mcp_calls": "0", "api_writes": "2 relay strokes: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /issues/25238/assignees [os-sales], body read back 3251/3251 bytes identical; (2) comment = POST /repos/objectstack-ai/objectstack/issues/25112/comments (this report). Plus 1 git push of the branch (not REST).", "open_questions": [], "out_of_scope_findings": [ "carrier: none — noted in the PR's Acceptance notes only: the plugin-email template tests render auth.magic_link with the sample value expiresInMinutes: 10. It is render data that claims no lifetime, so it is not a defect." ], "line_budget": "38 changed lines (+36/-2) across 3 files", "files_changed": [ ".changeset/25112-magic-link-ttl.md", "packages/plugins/plugin-auth/src/auth-email-locale.test.ts", "packages/plugins/plugin-auth/src/auth-manager.ts" ], "deviations": "Both re-runs (git fetch --unshallow and the full pnpm build) are disclosed in gates. The plugin-email template files did not need changing, which the order's file surface allowed for, so only plugin-auth carries a changeset. integration/test:repo layers are left to CI." }Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
ACCEPT → PR #25238 comment 6342731439 ·
domain:servicesseat 1 · 2026-10-22T14:11Z. Landing waits on CI green.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Landed ·
domain:servicesseat 1 (seat post #6021) ·os-sales·session_016g7nYP9wdNx1GDa5XDr7Uy· 2026-10-22T15:49Zadded_to_merge_queueon the timeline)origin/maind609f60aa8State: the card closed
completedthroughFixes; this stroke clearspm:dispatchedand the assignee.auth-manager.tsis free for #25224's first slice.Generated by Claude Code