Skip to content

fix: resolve bugs surfaced by the personas QA workflow - #79

Merged
Wifsimster merged 1 commit into
mainfrom
claude/workflow-personas-bugs-1ufwb
Jun 2, 2026
Merged

fix: resolve bugs surfaced by the personas QA workflow#79
Wifsimster merged 1 commit into
mainfrom
claude/workflow-personas-bugs-1ufwb

Conversation

@Wifsimster

Copy link
Copy Markdown
Owner

What

Ran a personas QA workflow — walked the app as three distinct user personas (widget end-user, admin operator, ops/self-hoster) to surface real bugs, then fixed the concrete, verified ones. Speculative findings, intentional design choices, and large feature gaps were deliberately left out to keep this focused and low-risk.

Fixes

Ops / self-hoster

  • serve.ts — PORT NaN guard. Number(process.env.PORT ?? 8787) returns NaN for a stray value like 8787/tcp, which then binds an arbitrary port. Now validated with a loud fallback to the default.
  • serve.ts — close Redis on shutdown. The shared ioredis socket and its reconnect timers kept the event loop alive past server.close(), so the process never exited cleanly and always hit the 10s hard-deadline exit(1) on every rolling deploy. Added closeRedis() (lib/redis.ts) and called it in the drain path.
  • serve.ts — entrypoint guard. Switched from import.meta.url === file://${argv[1]} to the argv-basename check already used (and documented) in migrate.ts, which is robust to bundler inlining. Handles both the compiled .js image and tsx dev (.ts).
  • index.tsADMIN_SESSION_TTL_DAYS NaN guard. A non-numeric value made expiresAt = Date.now() + NaNNaN, so logins "succeeded" (200) but the next request 401'd with no obvious cause. Now falls back to the 7-day default.
  • rotate-secrets.ts — counter mislabel. Retired-kid rows skipped without --reencrypt-all were counted as skipped-active, making the summary imply rotation was complete while rows still needed re-encryption. They now report under a new skipped-other counter.

Admin operator

  • adminAuth.ts — remove obsolete TOFU branch (logout bypass). Login now persists sessions via recordAdminSession, so the trust-on-first-use branch re-admitted a never-recorded cookie — letting a replayed cookie survive logout until HMAC expiry and defeating server-side revocation. A missing admin_sessions row now correctly 401s. The in-code comment had already flagged this branch for removal once login persisted.
  • dashboard client doc. Corrected the listTickets comment: recent sorts by created_at desc (tie-broken by id), not updated_at (verified against the server query).

Widget end-user

  • locales.ts — locale auto-detection. Used || instead of ?? when reading navigator.language; some browsers expose it as an empty string (not nullish), which short-circuited ?? and discarded a valid navigator.languages[0], rendering the default locale to a non-English user.

Verification

  • pnpm --filter @koe/api test89/89 pass
  • typecheck green across @koe/api, @koe/shared, @koe/dashboard, @wifsimster/koe
  • Prettier clean on all changed files
  • CLAUDE.md updated to reflect the removed TOFU branch

Not included (intentionally)

  • Inbox pagination (server supports cursors; dashboard caps at 50) — real, but a UI feature rather than a contained fix.
  • Bulk-action batchId revert correlation — the code has an explicit design comment choosing per-event undo, and it'd need a schema migration.
  • X-Forwarded-For left-most-hop trust — deployment-topology-specific; a blind change could break legitimate setups.

https://claude.ai/code/session_01UgPHbsFRraBXDg5BXDyQRQ


Generated by Claude Code

Walked the app as distinct user personas (widget end-user, admin
operator, ops/self-hoster) and fixed concrete, verified defects:

ops / self-hoster
- serve.ts: guard PORT parsing against non-numeric values (e.g.
  "8787/tcp") that produced NaN and bound an arbitrary port.
- serve.ts: close the shared Redis connection on SIGTERM/SIGINT.
  Its open socket + reconnect timers kept the event loop alive past
  server.close(), forcing the hard-deadline exit(1) on every rolling
  deploy. Adds closeRedis() to lib/redis.ts.
- serve.ts: detect the entrypoint via argv basename instead of
  import.meta.url, matching the hardened guard in migrate.ts (robust
  to bundler inlining; handles both compiled .js and tsx .ts).
- index.ts: guard ADMIN_SESSION_TTL_DAYS against NaN, which made every
  minted cookie expire at NaN so logins "succeeded" but the next
  request 401'd.
- rotate-secrets.ts: stop counting retired-kid rows skipped without
  --reencrypt-all as "skipped-active"; report them under a new
  skipped-other counter so the summary no longer implies rotation is
  complete while rows still need re-encryption.

admin operator
- adminAuth.ts: remove the obsolete TOFU branch. Login now persists
  sessions via recordAdminSession, so re-admitting a never-recorded
  cookie let a replayed cookie survive logout until HMAC expiry —
  defeating server-side revocation. A missing session row now 401s.
- dashboard client: correct the listTickets doc — `recent` sorts by
  created_at desc (tie-broken by id), not updated_at.

widget end-user
- locales.ts: use `||` instead of `??` when reading navigator.language
  so an empty-string value (some browsers) falls through to
  navigator.languages[0] instead of rendering the default locale.

https://claude.ai/code/session_01UgPHbsFRraBXDg5BXDyQRQ
@Wifsimster
Wifsimster marked this pull request as ready for review June 2, 2026 06:00
@Wifsimster
Wifsimster merged commit b60022d into main Jun 2, 2026
1 check passed
@Wifsimster
Wifsimster deleted the claude/workflow-personas-bugs-1ufwb branch June 2, 2026 06:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants