Skip to content

fix: CSP Clerk-origin drift + Production Smoke failure paging (linejam-912, linejam-913) - #300

Closed
moomooskycow wants to merge 9 commits into
masterfrom
linejam-overhaul-0705
Closed

fix: CSP Clerk-origin drift + Production Smoke failure paging (linejam-912, linejam-913)#300
moomooskycow wants to merge 9 commits into
masterfrom
linejam-overhaul-0705

Conversation

@moomooskycow

@moomooskycow moomooskycow commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Linejam deep-cleanup overhaul following the 2026-07-04 16-hour outage. All 5
incident cards plus 3 application-floor gaps, each independently live-verified:

  • linejam-912 (P1): CSP Clerk origins now derive from
    NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY at config time instead of a hand-listed
    domain array (the exact bug that blocked auth site-wide for ~16h).
  • linejam-913 (P0): Production Smoke now reports every run to a new
    linejam-production-smoke Canary TTL monitor. A single failure is recorded
    without escalating; two consecutive failures open a real Canary incident
    (live-verified: opened and auto-resolved a real incident against the
    deployed Canary instance).
  • linejam-914 (P1): a CI gate (scripts/ci/check-schema-migration-sequencing.mjs)
    blocks any PR that both removes a convex/schema.ts field and adds its
    migration in the same diff — the exact class of bug that wedged every
    deploy on 07-04. Regression test replays the actual outage commit's diff.
  • linejam-915 (P1): /releases was frozen at v0.1.0 for months while the
    app was on v1.15.1 (two competing stores, one dead). Now release.yml
    writes content/releases/ deterministically on every release; backfilled
    all 21 historical versions; a test gates manifest.json against
    package.json's version on every run.
  • linejam-916 (P1): root-caused the broken Release workflow to a personal
    gh auth login token pasted into a repo secret that silently broke when
    the repo went public; switched to the job's own ephemeral GITHUB_TOKEN.
  • linejam-909/910/911 (P3, application-floor gaps): pnpm doctor
    (verified-live onboarding check), a Playwright smoke matrix across major
    pages at desktop + ~390px asserting zero console errors, and an
    evidence-based coverage ratchet (85% → 89-90%, from 91.44%/86.32%/92.75%/92.9%
    measured).

Also deleted a dead, untested, never-wired release-notes generator with an
ungated raw LLM call that could have reintroduced the split-brain
(scripts/generate-releases.ts + lib/releases/parser.ts).

Fresh-context critic pass

Two independent fresh-context critics (no access to this session's reasoning)
reviewed the finished branch. One found a real bug — doctor's app-health
check defaulted to the wrong port — fixed and live-verified before this PR
was finalized. The other drove a real Chromium browser at 390px/1440px
against a real production build and confirmed the releases fix renders
correctly with no console errors (the actual Vercel preview is SSO-gated and
unreachable from this environment — documented, not silently skipped).

Test plan

  • pnpm exec vitest run — 117 files / 1256 tests pass, 1 pre-existing skip
  • pnpm test:ci (coverage) — exit 0 against ratcheted thresholds
  • pnpm run typecheck:app / pnpm run lint — clean
  • actionlint .github/workflows/*.yml — clean
  • All CI checks green: Quality Gates, Test & Build, E2E Mirror, Early
    Selector Smoke, QA Evidence, merge-gate, trufflehog
  • Live proof: real Canary incident opened + auto-resolved; real next build showing the releases fix; fresh-context critics found and one
    confirmed bug fixed
  • linejam-916's full acceptance ("next merge produces a green Release
    run") needs an actual merge to verify, per its own criterion

Powder: linejam-912, linejam-913, linejam-914, linejam-915, linejam-916,
linejam-909, linejam-910, linejam-911 (all done)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Production smoke checks now send Canary health check-ins and include consecutive failure context in workflow results.
    • Security headers automatically derive the correct Clerk origin from the configured publishable key.
    • Added a pnpm doctor readiness check to the development workflow.
  • Bug Fixes
    • Improved resiliency of smoke-failure escalation and safer handling when Clerk origin can’t be derived.
  • Documentation
    • Added documentation for how smoke failures escalate Canary monitoring.
    • Updated testing and release guidance to match the latest workflows.

… list

PR #291/#299 hand-listed Clerk domains in next.config.ts and missed the
production custom domain (clerk.linejam.app), blocking auth site-wide for
~16h on 2026-07-04 while preview smoke stayed green (previews use the
allowed dev Clerk domain). A hand-maintained list can drift from the live
Clerk config again the next time the domain changes.

Extract the existing pk_(test|live)_<base64url(host)> decoder (previously
duplicated between bootstrap-convex-env.mjs and dagger-call.sh) into
scripts/lib/clerk-domain.mjs as the one canonical implementation, and have
next.config.ts derive its Clerk CSP sources from it at config-eval time.
The static clerk.linejam.app assertion stays as a regression tripwire;
new tests prove genuine derivation against a different custom domain and
against a dev/preview-style key.

Powder: linejam-912

Agent: linejam-overhaul
Agent-Surface: Claude Code
Agent-Model: anthropic/claude-fable-5
Agent-Task: linejam-912
2026-07-04 outage: Production Smoke failed hourly for ~15h before the
operator found the outage by hand. The gate was working; nothing wired the
red signal to a human or to BB triage.

Add a linejam-production-smoke Canary TTL monitor (expected_every_ms=1h,
grace_ms=30m, live at MON-28junwbo5mgv) and report every run's outcome to
it via POST /api/v1/check-ins, reusing the existing NEXT_PUBLIC_CANARY_API_KEY/
NEXT_PUBLIC_CANARY_ENDPOINT secrets (no new secret provisioned):

- success -> "ok" (Up; resolves any open incident)
- failure, streak < 2 -> "alive" (Up; recorded, not escalated -- a single
  blip is an annotation)
- failure, streak >= 2 -> "error" (Canary maps this directly to its Down
  health state, opening/holding a health_transition incident that BB
  triage and the bridge feed already consume)

The consecutive-failure streak is computed from the workflow's own run
history via the GitHub API (scripts/ops/count-consecutive-prod-smoke-failures.mjs),
failing OPEN toward escalation rather than silence if that lookup errors.
A GitHub step-summary annotation is written on every failure regardless of
escalation.

Live-verified end to end against the deployed monitor: first simulated
failure stayed up/alive, second consecutive failure flipped to down and
opened INC-bhg3g284flhp, and a simulated recovery run resolved it
immediately (see docs/ops/canary-responder.md for the full transcript).

Powder: linejam-913

Agent: linejam-overhaul
Agent-Surface: Claude Code
Agent-Model: anthropic/claude-fable-5
Agent-Task: linejam-913
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@cursor

cursor Bot commented Jul 5, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes production auth (CSP), release workflow pushes to master, and prod incident paging, but each path is regression-tested and targets known outage classes rather than new product behavior.

Overview
Post-2026-07-04 outage hardening: closes gaps where preview stayed green while production auth/CSP broke, prod smoke stayed red without paging, releases drifted, and Convex schema+migration could ship in one PR.

Auth / CSP (linejam-912): CSP Clerk allowlists are derived from NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY via shared scripts/lib/clerk-domain.mjs (also used by Convex bootstrap and pnpm doctor), replacing a hand-listed production domain that went stale.

Production smoke → Canary (linejam-913): prod-smoke.yml counts consecutive failures, posts check-ins to the linejam-production-smoke monitor (escalate at 2 failures), and annotates the workflow summary; smoke stderr is captured for context.

CI / Convex (linejam-914): PR quality gates use full git history and run check-schema-migration-sequencing.mjs to block removing convex/schema.ts fields in the same PR that adds the migration.

Releases (linejam-915/916): Landmark uses GITHUB_TOKEN instead of a personal GH_RELEASE_TOKEN; on release, write-release-from-git.mjs updates content/releases/ and commits to master. Manual pnpm generate:releases and the old CHANGELOG/LLM parser are removed; manifest is backfilled and gated against package.json.

Onboarding / floor: Adds pnpm doctor, Playwright major-page smoke (desktop + mobile), coverage ratchet in Vitest, and join-page unit tests; docs for migrations and the static release store.

Reviewed by Cursor Bugbot for commit 65de13f. Configure here.

@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
linejam Ready Ready Preview, Comment Jul 5, 2026 1:04am

@cursor

cursor Bot commented Jul 5, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_6eeab58b-f9e0-4cf8-83c4-ad0896959dc7)

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds production smoke escalation reporting, shared Clerk origin derivation, schema-migration guardrails, onboarding diagnostics, and a static release-store pipeline with matching workflow, documentation, test, and content updates.

Changes

Production Smoke Failure Escalation

Layer / File(s) Summary
Consecutive failure streak script
scripts/ops/count-consecutive-prod-smoke-failures.mjs, tests/scripts/count-consecutive-prod-smoke-failures.test.ts
Counts consecutive production smoke failures by querying prior workflow runs from GitHub and includes unit coverage for streak rules and API handling.
Canary check-in reporting script
scripts/ops/report-prod-smoke-status.mjs, tests/scripts/report-prod-smoke-status.test.ts
Maps smoke outcomes and streak counts into Canary check-in payloads, posts them to the Canary API, and exposes a CLI entrypoint with tests.
Workflow integration and docs
.github/workflows/prod-smoke.yml, docs/ops/canary-responder.md
Tightens workflow permissions, captures stderr tail as detail output, runs the new smoke-status scripts, and documents the escalation flow.

Clerk Frontend Origin Derivation

Layer / File(s) Summary
Shared Clerk origin derivation helper
scripts/lib/clerk-domain.mjs, scripts/ci/bootstrap-convex-env.mjs, tests/scripts/clerk-domain.test.ts
Adds a shared helper that derives a Clerk frontend origin from a publishable key and replaces inline derivation in the Convex bootstrap script, with unit tests for valid and invalid inputs.
CSP directive derivation in next.config.ts
next.config.ts, tests/next-config.test.ts
Builds CSP Clerk sources from the derived origin plus generic Clerk sources and updates tests for derived-origins and fallback behavior.

Doctor and Schema-Migration Guardrails

Layer / File(s) Summary
Doctor checks and onboarding
scripts/doctor.mjs, tests/scripts/doctor.test.ts, README.md, AGENTS.md, package.json
Adds a workspace doctor command that validates env and health checks, wires it into onboarding and command references, and adds tests for each check.
Schema-migration sequencing gate
scripts/ci/check-schema-migration-sequencing.mjs, tests/scripts/check-schema-migration-sequencing.test.ts, .github/workflows/ci.yml, docs/convex-migrations.md
Adds a CI guard that blocks schema field removals landing with new migrations, with workflow wiring, docs, and tests.
Ratcheted test/coverage docs
docs/testing.md, vitest.config.ts
Updates the documented and enforced coverage thresholds to the new ratcheted values.

Static Release Store and Release Generation

Layer / File(s) Summary
Release generation and store code
scripts/release/conventional-commits.mjs, scripts/release/static-release-store.mjs, scripts/release/write-release-from-git.mjs, scripts/release/backfill-static-releases.mjs
Adds conventional-commit parsing, static release entry writing, manifest regeneration, git-backed release synthesis, and historical backfill support.
Release workflow and package wiring
.github/workflows/release.yml, package.json
Switches the release workflow to GITHUB_TOKEN, writes static release content, and removes the legacy release-generator script while adding doctor.
Release store docs and index
docs/releases-static-store.md, lib/releases/index.ts
Updates release-store documentation and trims the releases index export surface to the static-store pipeline.
Release content and manifest data
content/releases/manifest.json, content/releases/v*/changelog.json
Refreshes the release manifest and adds or updates per-version changelog JSON content across the tracked release versions.
Release tooling tests
tests/scripts/conventional-commits.test.ts, tests/scripts/static-release-store.test.ts, tests/scripts/write-release-from-git.test.ts, tests/scripts/release-manifest-version.test.ts
Covers commit parsing, store writes, manifest regeneration, git-backed release writing, and manifest/package consistency.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • misty-step/linejam#263: This PR also changes .github/workflows/release.yml around Landmark release execution and token usage, overlapping with the release workflow wiring here.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main functional changes: Clerk-origin CSP derivation and production smoke failure paging.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch linejam-overhaul-0705

Comment @coderabbitai help to get the list of available commands.

…tion

2026-07-04 outage, part two: PR #298 removed convex/schema.ts fields and
added their migration (dropLegacyModeColumns) in the same commit. Convex
validates schema against live data at push time, so the contraction wedged
every deploy -- including the unrelated P0 CSP hotfix production needed
immediately -- until an operator ran the migration manually against
production, outside the normal pipeline.

Document the expand-migrate-contract sequence in docs/convex-migrations.md
with this incident as the worked example, and add a CI gate
(scripts/ci/check-schema-migration-sequencing.mjs, wired into the
quality-gates job for pull requests) that fails any PR whose diff both
removes a schema.ts field and adds a migrations.ts export. It's a plain
git-diff heuristic rather than a Dagger container, since it needs git
history/merge-base rather than the source tree. Its regression test
replays the actual PR #298 diff (git show 684de32) and the CLI is
separately live-verified to block against that same historical diff
(exit 1, naming the exact removed fields and added migration).

Annotated linejam-019 (the card #298 shipped under) with the sequencing
lesson.

Powder: linejam-914

Agent: linejam-overhaul
Agent-Surface: Claude Code
Agent-Model: anthropic/claude-fable-5
Agent-Task: linejam-914

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
scripts/ops/count-consecutive-prod-smoke-failures.mjs (1)

46-74: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider adding a request timeout to the GitHub API call.

fetchImpl here has no timeout, unlike the Canary POST in report-prod-smoke-status.mjs (which uses AbortSignal.timeout(5_000)). If the GitHub API hangs, this step blocks until the job's 15-minute timeout rather than failing fast into the existing catch-based fallback.

♻️ Proposed fix
   const response = await fetchImpl(url, {
     headers: {
       Authorization: `Bearer ${token}`,
       Accept: 'application/vnd.github+json',
       'X-GitHub-Api-Version': '2022-11-28',
     },
+    signal: AbortSignal.timeout(5_000),
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ops/count-consecutive-prod-smoke-failures.mjs` around lines 46 - 74,
The GitHub API request in fetchPriorRunConclusions has no timeout, so it can
hang until the job times out; add a fast-fail timeout like the one used in
report-prod-smoke-status.mjs. Update the fetchImpl call in
fetchPriorRunConclusions to pass an AbortSignal timeout (for example via
AbortSignal.timeout) and make sure the existing error handling still surfaces
the fetch failure cleanly. Use the fetchPriorRunConclusions symbol to locate the
request and keep the fallback behavior unchanged.
.github/workflows/prod-smoke.yml (1)

93-100: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Prefer env-var indirection for ${{ steps.smoke.outcome }} to avoid the flagged template-injection pattern.

zizmor flags this expansion directly into the shell command. steps.<id>.outcome is a GitHub-controlled enum (success/failure/cancelled/skipped), so exploitability is low here, but routing it through env: avoids the pattern entirely and is the standard mitigation for this class of finding.

🔒 Proposed fix
       - name: Determine consecutive-failure streak
         id: streak
         if: always()
         env:
           GITHUB_TOKEN: ${{ github.token }}
+          SMOKE_OUTCOME: ${{ steps.smoke.outcome }}
         run: |
-          count="$(node scripts/ops/count-consecutive-prod-smoke-failures.mjs '${{ steps.smoke.outcome }}')"
+          count="$(node scripts/ops/count-consecutive-prod-smoke-failures.mjs "$SMOKE_OUTCOME")"
           echo "count=$count" >> "$GITHUB_OUTPUT"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/prod-smoke.yml around lines 93 - 100, The workflow step in
Determine consecutive-failure streak is directly interpolating
steps.smoke.outcome into the shell command, which triggers the
template-injection warning. Move that GitHub expression into an env variable on
the same step, then have the run script read from that env value instead of
embedding the expression inline. Keep the change localized to the streak step in
prod-smoke.yml and preserve the existing count invocation and GITHUB_OUTPUT
write.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/prod-smoke.yml:
- Around line 49-61: The smoke job output block uses a fixed multiline
delimiter, which can be collided with by captured stderr and corrupt the GitHub
Actions output parsing. Update the `id: smoke` step in the workflow to generate
a unique per-run delimiter before writing to `GITHUB_OUTPUT`, and use that
dynamic marker for the `detail` multiline value instead of the hardcoded
`SMOKE_DETAIL_EOF`.

In `@docs/ops/canary-responder.md`:
- Line 117: The phrase in the canary responder documentation should use the
standard compound modifier form; update the sentence containing “end to end” in
the docs text to “end-to-end.” Keep the surrounding wording unchanged and make
the edit in the same prose block so the language is consistent throughout the
canary-responder.md content.

In `@scripts/lib/clerk-domain.mjs`:
- Around line 25-26: The domain extraction in clerk-domain.mjs is using
split('_').at(-1), which can lose part of the base64url payload when the encoded
segment contains underscores. Update the parsing logic in the key-handling flow
so the function that derives encodedDomain strips the pk_(test|live)_ prefix
directly instead of splitting on every underscore, then continue decoding the
remaining payload as before.

In `@scripts/ops/report-prod-smoke-status.mjs`:
- Around line 124-151: The `run` function in `report-prod-smoke-status.mjs`
currently parses `LINEJAM_SMOKE_CONSECUTIVE_FAILURES` with a default of 0, which
can cause missing or invalid streak values to be treated as non-escalated.
Update the `consecutiveFailures` handling so empty, non-numeric, or otherwise
invalid values fall back to `ESCALATION_THRESHOLD` (or validate before parsing)
before calling `planCheckIn`, and keep the `context` object aligned with the
sanitized streak value.

---

Nitpick comments:
In @.github/workflows/prod-smoke.yml:
- Around line 93-100: The workflow step in Determine consecutive-failure streak
is directly interpolating steps.smoke.outcome into the shell command, which
triggers the template-injection warning. Move that GitHub expression into an env
variable on the same step, then have the run script read from that env value
instead of embedding the expression inline. Keep the change localized to the
streak step in prod-smoke.yml and preserve the existing count invocation and
GITHUB_OUTPUT write.

In `@scripts/ops/count-consecutive-prod-smoke-failures.mjs`:
- Around line 46-74: The GitHub API request in fetchPriorRunConclusions has no
timeout, so it can hang until the job times out; add a fast-fail timeout like
the one used in report-prod-smoke-status.mjs. Update the fetchImpl call in
fetchPriorRunConclusions to pass an AbortSignal timeout (for example via
AbortSignal.timeout) and make sure the existing error handling still surfaces
the fetch failure cleanly. Use the fetchPriorRunConclusions symbol to locate the
request and keep the fallback behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d406cdf-c517-49e6-9a09-55bf734faecb

📥 Commits

Reviewing files that changed from the base of the PR and between 8b8da91 and 9e17782.

📒 Files selected for processing (10)
  • .github/workflows/prod-smoke.yml
  • docs/ops/canary-responder.md
  • next.config.ts
  • scripts/ci/bootstrap-convex-env.mjs
  • scripts/lib/clerk-domain.mjs
  • scripts/ops/count-consecutive-prod-smoke-failures.mjs
  • scripts/ops/report-prod-smoke-status.mjs
  • tests/next-config.test.ts
  • tests/scripts/count-consecutive-prod-smoke-failures.test.ts
  • tests/scripts/report-prod-smoke-status.test.ts

Comment on lines +49 to +61
id: smoke
run: |
mkdir -p "$RUNNER_TEMP/linejam-smoke"
set +e
pnpm canary:smoke > "$RUNNER_TEMP/linejam-smoke/stdout.log" 2> "$RUNNER_TEMP/linejam-smoke/stderr.log"
code=$?
cat "$RUNNER_TEMP/linejam-smoke/stdout.log"
cat "$RUNNER_TEMP/linejam-smoke/stderr.log" >&2
{
echo "detail<<SMOKE_DETAIL_EOF"
tail -c 2000 "$RUNNER_TEMP/linejam-smoke/stderr.log"
echo "SMOKE_DETAIL_EOF"
} >> "$GITHUB_OUTPUT"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the workflow around the referenced lines
file=".github/workflows/prod-smoke.yml"
wc -l "$file"
sed -n '1,160p' "$file"

echo
echo "---- search for LINEJAM_SMOKE_FAILURE_DETAIL and steps.smoke.outputs.detail ----"
rg -n "LINEJAM_SMOKE_FAILURE_DETAIL|steps\.smoke\.outputs\.detail|SMOKE_DETAIL_EOF|GITHUB_OUTPUT" .github/workflows . -g '*.yml' -g '*.yaml' -g '*.md'

Repository: misty-step/linejam

Length of output: 5521


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect GitHub Actions docs locally? (repo files only)
rg -n "multiline|GITHUB_OUTPUT|delimiter|random" .github README.md docs -g '*.md' -g '*.yml' -g '*.yaml' || true

Repository: misty-step/linejam

Length of output: 837


Use a unique GITHUB_OUTPUT delimiter here. A fixed SMOKE_DETAIL_EOF marker can appear in the captured stderr and truncate the multiline value, letting extra text be parsed as new outputs. Generate a per-run delimiter instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/prod-smoke.yml around lines 49 - 61, The smoke job output
block uses a fixed multiline delimiter, which can be collided with by captured
stderr and corrupt the GitHub Actions output parsing. Update the `id: smoke`
step in the workflow to generate a unique per-run delimiter before writing to
`GITHUB_OUTPUT`, and use that dynamic marker for the `detail` multiline value
instead of the hardcoded `SMOKE_DETAIL_EOF`.


Reuses the existing `NEXT_PUBLIC_CANARY_API_KEY`/`NEXT_PUBLIC_CANARY_ENDPOINT` repository secrets (already ingest-scoped for client-side error reporting); no new secret was provisioned.

Live-verified end to end against the deployed monitor: a first simulated failure stayed `up`/`alive`; a second consecutive simulated failure flipped the monitor to `down` and opened `INC-bhg3g284flhp` (`signal_type: health_transition`); a simulated recovery run immediately resolved it. Query current state any time with:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Minor: hyphenate "end-to-end."

LanguageTool flags this as a compound-modifier hyphenation opportunity.

✏️ Proposed fix
-Live-verified end to end against the deployed monitor: a first simulated failure stayed `up`/`alive`; a second consecutive simulated failure flipped the monitor to `down` and opened `INC-bhg3g284flhp` (`signal_type: health_transition`); a simulated recovery run immediately resolved it. Query current state any time with:
+Live-verified end-to-end against the deployed monitor: a first simulated failure stayed `up`/`alive`; a second consecutive simulated failure flipped the monitor to `down` and opened `INC-bhg3g284flhp` (`signal_type: health_transition`); a simulated recovery run immediately resolved it. Query current state any time with:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Live-verified end to end against the deployed monitor: a first simulated failure stayed `up`/`alive`; a second consecutive simulated failure flipped the monitor to `down` and opened `INC-bhg3g284flhp` (`signal_type: health_transition`); a simulated recovery run immediately resolved it. Query current state any time with:
Live-verified end-to-end against the deployed monitor: a first simulated failure stayed `up`/`alive`; a second consecutive simulated failure flipped the monitor to `down` and opened `INC-bhg3g284flhp` (`signal_type: health_transition`); a simulated recovery run immediately resolved it. Query current state any time with:
🧰 Tools
🪛 LanguageTool

[grammar] ~117-~117: Use a hyphen to join words.
Context: ...cret was provisioned. Live-verified end to end against the deployed monitor: a firs...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ops/canary-responder.md` at line 117, The phrase in the canary responder
documentation should use the standard compound modifier form; update the
sentence containing “end to end” in the docs text to “end-to-end.” Keep the
surrounding wording unchanged and make the edit in the same prose block so the
language is consistent throughout the canary-responder.md content.

Source: Linters/SAST tools

Comment on lines +25 to +26
const encodedDomain = key.split('_').at(-1);
if (!encodedDomain) return '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
node -e '
const hosts = ["clerk.linejam.app","auth.example-tenant.com","great-moose-1.clerk.accounts.dev"];
// brute-force a few synthetic hosts to prove _ is reachable in base64url output
for (let i=0;i<5000;i++){ hosts.push("clerk-"+i+".linejam.app"); }
let hit=null;
for (const h of hosts){
  const enc = Buffer.from(h+"$").toString("base64url");
  if (enc.includes("_")){ hit={h,enc}; break; }
}
console.log("example host whose key segment contains _ :", hit);
if (hit){
  const key = "pk_live_"+hit.enc;
  const tail = key.split("_").at(-1);
  console.log("split-based decode:", Buffer.from(tail,"base64url").toString("utf8"));
  console.log("prefix-strip decode:", Buffer.from(hit.enc,"base64url").toString("utf8"));
}
'

Repository: misty-step/linejam

Length of output: 205


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== scripts/lib/clerk-domain.mjs ==\n'
sed -n '1,220p' scripts/lib/clerk-domain.mjs

printf '\n== related references ==\n'
rg -n "deriveClerkFrontendOrigin|clerk-domain|frontendApiHost|Convex JWT issuer|bootstrap-convex-env|next.config" scripts . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || true

printf '\n== tests around clerk domain ==\n'
rg -n "clerk.*domain|frontendApiHost|base64url|pk_(test|live)_" scripts test tests . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || true

Repository: misty-step/linejam

Length of output: 14127


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file inventory ==\n'
git ls-files | rg '^scripts/lib/clerk-domain\.mjs$|^scripts/.*(bootstrap-convex-env|next\.config|clerk).*|(^|/)(test|tests)/.*clerk|.*clerk.*test.*'

printf '\n== source around the target ==\n'
nl -ba scripts/lib/clerk-domain.mjs | sed -n '1,120p'

Repository: misty-step/linejam

Length of output: 473


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and nearby callers/tests without executing repository code.
printf '\n== scripts/lib/clerk-domain.mjs ==\n'
nl -ba scripts/lib/clerk-domain.mjs | sed -n '1,200p'

printf '\n== references to the helper and related derivation ==\n'
rg -n "deriveClerkFrontendOrigin|clerk-domain|frontendApiHost|base64url|pk_(test|live)_" scripts . \
  -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || true

printf '\n== test files referencing clerk domain ==\n'
rg -n "clerk.*domain|frontendApiHost|base64url|pk_(test|live)_" tests test scripts \
  -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || true

Repository: misty-step/linejam

Length of output: 232


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import base64
import itertools
import string

alphabet = string.ascii_lowercase + string.digits + "-."
# Search short, syntactically plausible host labels first.
def enc(s: str) -> str:
    return base64.urlsafe_b64encode(s.encode()).decode().rstrip("=")

for n in range(1, 7):
    for s in itertools.product(alphabet, repeat=n):
        host = "".join(s)
        e = enc(host)
        if "_" in e:
            print("found_host=", host)
            print("encoded=", e)
            key = "pk_live_" + e
            tail = key.split("_")[-1]
            print("split_tail=", tail)
            print("split_decoded=", base64.urlsafe_b64decode(tail + "==").decode("utf-8", "replace"))
            print("full_decoded=", base64.urlsafe_b64decode(e + "==").decode("utf-8", "replace"))
            raise SystemExit(0)
print("no host found in search space")
PY

Repository: misty-step/linejam

Length of output: 148


split('_').at(-1) can truncate the encoded segment

pk_(test|live)_<base64url(...)> keys can carry _ inside the base64url payload, so splitting on every underscore can drop everything before the last _ and decode the wrong host. Strip the pk_(test|live)_ prefix instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/lib/clerk-domain.mjs` around lines 25 - 26, The domain extraction in
clerk-domain.mjs is using split('_').at(-1), which can lose part of the
base64url payload when the encoded segment contains underscores. Update the
parsing logic in the key-handling flow so the function that derives
encodedDomain strips the pk_(test|live)_ prefix directly instead of splitting on
every underscore, then continue decoding the remaining payload as before.

Comment on lines +124 to +151
export async function run({
outcome = process.env.LINEJAM_SMOKE_OUTCOME,
consecutiveFailures = Number.parseInt(
process.env.LINEJAM_SMOKE_CONSECUTIVE_FAILURES || '0',
10
),
runUrl = process.env.LINEJAM_SMOKE_RUN_URL,
failureDetail = process.env.LINEJAM_SMOKE_FAILURE_DETAIL,
env = process.env,
fetchImpl = globalThis.fetch,
} = {}) {
if (outcome !== 'success' && outcome !== 'failure') {
throw new Error(
`LINEJAM_SMOKE_OUTCOME must be "success" or "failure", got: ${outcome}`
);
}

const plan = planCheckIn({ outcome, consecutiveFailures });
const context = {
consecutiveFailures,
...(runUrl ? { runUrl } : {}),
...(outcome === 'failure' && failureDetail?.trim()
? { failureDetail: truncate(failureDetail.trim(), 1_000) }
: {}),
};

return sendCheckIn({ ...plan, context, env, fetchImpl });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== scripts/ops/report-prod-smoke-status.mjs ==\n'
wc -l scripts/ops/report-prod-smoke-status.mjs
sed -n '1,220p' scripts/ops/report-prod-smoke-status.mjs

printf '\n== scripts/ops/count-consecutive-prod-smoke-failures.mjs ==\n'
wc -l scripts/ops/count-consecutive-prod-smoke-failures.mjs
sed -n '1,220p' scripts/ops/count-consecutive-prod-smoke-failures.mjs

printf '\n== .github/workflows/prod-smoke.yml ==\n'
wc -l .github/workflows/prod-smoke.yml
sed -n '1,220p' .github/workflows/prod-smoke.yml

Repository: misty-step/linejam

Length of output: 12956


Treat missing or invalid streak counts as escalated.
LINEJAM_SMOKE_CONSECUTIVE_FAILURES can be empty or non-numeric if the streak step fails, and this path falls back to 0/NaN, so a failed smoke run is reported as alive instead of error. The summary step also breaks on a missing count. Fallback to ESCALATION_THRESHOLD (or validate the env before parsing) instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ops/report-prod-smoke-status.mjs` around lines 124 - 151, The `run`
function in `report-prod-smoke-status.mjs` currently parses
`LINEJAM_SMOKE_CONSECUTIVE_FAILURES` with a default of 0, which can cause
missing or invalid streak values to be treated as non-escalated. Update the
`consecutiveFailures` handling so empty, non-numeric, or otherwise invalid
values fall back to `ESCALATION_THRESHOLD` (or validate before parsing) before
calling `planCheckIn`, and keep the `context` object aligned with the sanitized
streak value.

…-brain

Two entangled release-pipeline failures from 2026-07-04, fixed together
because linejam-915's fix depends on linejam-916's:

linejam-916 (why Release stopped working): the Release workflow used
GH_RELEASE_TOKEN, a personal gh-CLI OAuth token pasted into a repo secret,
which started 403ing ("You do not have permission to create labels on this
repository") the moment linejam went public (PR #296) -- verified against
the actual failed-run logs (x-oauth-client-id matches the GitHub CLI's own
OAuth App). master has no branch protection and no rulesets (verified via
the API), so nothing here needs bypassing: switch to the job's own
GITHUB_TOKEN, whose already-declared contents/issues/pull-requests write
permissions are exactly what semantic-release + @semantic-release/github
need. No new secret provisioned -- GITHUB_TOKEN is ephemeral per run.
Verified structurally (no branch protection to fight) and functionally (a
throwaway label create/delete against this exact repo endpoint with an
equivalently-scoped token succeeded); full end-to-end proof needs the next
actual merge to master, per the card's own acceptance criterion.

linejam-915 (the split-brain itself): app/releases/page.tsx read
content/releases/, untouched since v0.1.0 (Jan 2026), while Landmark kept a
second store (docs/releases/feed.xml RSS) current through v1.15.1. Fix:
release.yml now writes content/releases/ on every release
(scripts/release/write-release-from-git.mjs), deriving the technical
changelog deterministically from git history (no LLM in that path) and
using Landmark's own synthesized notes for prose (the same synthesis
already producing every GH Release body and RSS entry in this repo --
not a new trust surface; its known fabrication risk, landmark-907, is
tracked upstream). manifest.json is regenerated from the version
directories actually on disk, so it can't itself drift.
scripts/release/backfill-static-releases.mjs backfilled all 21 historical
v1.x tags (deterministic changelog.json only, no fabricated prose).
tests/scripts/release-manifest-version.test.ts gates manifest.json against
package.json's version on every test run.

Live-verified end to end via a real `next build` (force-static
prerendering, the actual pipeline app/releases/page.tsx runs through): the
generated HTML shows "Version 1.15.1" first, followed by all 22 versions
in descending order down to 0.1.0, with real (non-fabricated) technical
detail entries pulled from actual commits.

Powder: linejam-915, linejam-916

Agent: linejam-overhaul
Agent-Surface: Claude Code
Agent-Model: anthropic/claude-fable-5
Agent-Task: linejam-915,linejam-916
…on fixture

CI's Test & Build job runs unit tests inside a Dagger container that
copies the working tree but not .git (a hermetic source-tree snapshot,
by design). The PR #298 regression fixture called `git show 684de32` at
test time, which failed there with "fatal: not a git repository" even
though it passed locally and in the plain-runner quality-gates job.

Embed the two diffs (schema.ts, migrations.ts) as literal fixture strings
instead -- they are frozen historical data, not something that needs to
be re-derived from a live repository at test time.

Found live: PR #300's Test & Build check failed on push; root-caused from
the actual job log rather than guessed.

Agent: linejam-overhaul
Agent-Surface: Claude Code
Agent-Model: anthropic/claude-fable-5
Agent-Task: linejam-914
linejam-909: scripts/setup.sh installs dependencies and writes placeholder
.env.local, then prints "setup complete" -- an installed-but-dead workspace
looked identical to a working one (application-floor item 9, the exact
Counterspell case study: onboarding must end at verified-live, not
installed).

pnpm doctor checks required env, that the Convex URL looks like a real
deployment, that the Clerk publishable key actually decodes to a Frontend
API host (not just "is present"), that Canary config isn't the
.env.example placeholder, and probes both Canary and the running app's
/api/health -- warning rather than failing when the app or Canary aren't
reachable yet (expected before `pnpm dev`), but failing hard on missing or
placeholder secrets. Wired into README and scripts/setup.sh's completion
message.

Writing doctor's tests surfaced a real latent bug in scripts/lib/clerk-domain.mjs
(linejam-912): base64url decoding rarely throws on arbitrary input, so a
malformed NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY silently decoded to
control-character garbage and reported success. Hardened it to reject
anything that doesn't look like a real hostname.

Also deleted scripts/generate-releases.ts + lib/releases/parser.ts (dead
code discovered while building linejam-915): a pre-existing, untested,
never-wired manual generator that called OpenRouter directly with a bare
prompt and no fallback protection -- the exact fabrication risk
landmark-907 exists to fix, and a second writer to content/releases/ that
could have reintroduced this card's split-brain the moment anyone ran it.

Live evidence (both required by linejam-909's proof plan):
  env -i node scripts/doctor.mjs            # 4 failing checks, exit 1
  (source real .env.local) node scripts/doctor.mjs  # all pass/warn, exit 0

Powder: linejam-909

Agent: linejam-overhaul
Agent-Surface: Claude Code
Agent-Model: anthropic/claude-fable-5
Agent-Task: linejam-909
linejam-910 (application-floor real-engine tier b): existing E2E specs
(game-flow, auth, favorites, room-chrome-layout, ...) cover behavioral
golden paths well, but nothing smoke-loaded every major page asserting
zero console/pageerror events at both a desktop and a mobile viewport --
the exact gap the Sanctum-artifacts case study (a raw-string escaping bug
that broke all client JS while 84 substring-matching tests stayed green)
names as the floor's reason for existing.

Covers '/', '/host', '/join', '/releases' (this overhaul's own linejam-915
fix), the poem/recap not-found shapes, and the /me/* protected-route
redirect, each at 1440x900 and iPhone 14 (~390px), asserting a visible
landmark and zero console.error/pageerror events. No new CI wiring needed:
Playwright auto-discovers every spec under tests/e2e/, so it runs inside
the existing `pnpm test:e2e` invocation the `e2e` job already calls via
Dagger on every non-draft PR.

Live-verified: PR #300's "E2E Mirror" check passed with this spec included
(https://github.com/misty-step/linejam/actions/runs/28724393292).

Powder: linejam-910

Agent: linejam-overhaul
Agent-Surface: Claude Code
Agent-Model: anthropic/claude-fable-5
Agent-Task: linejam-910
linejam-911: coverage was measured at 90.9%/85.94%/92.08%/92.32%
(stmts/branch/fn/lines) against a static 85% floor -- real headroom the
gate never captured, so a genuine regression could land undetected until
it dropped a full 5-6 points.

Evidence-first, not a blind bump:
- Identified app/join/page.tsx as the lowest-covered major page in the
  repo (48%/37%, zero tests) via `pnpm test:ci`'s per-file report --
  exactly the highest-risk gap the card asks to close. Added
  tests/app/join-page.test.tsx (loading/auth-error/prefill/disabled-submit/
  success/whitespace-strip/failure-path), taking it to 97%/89%.
- Extracted the untested CLI-argv-parsing/notes-reading logic out of
  scripts/release/write-release-from-git.mjs (28% covered, all of it my
  own new code from this session) into parseCliArgs/readNotesFile/
  defaultExec and tested them directly (65% covered now); did the same
  for scripts/ci/check-schema-migration-sequencing.mjs's message
  formatting (formatViolationMessage).
- Re-measured (91.44%/86.32%/92.75%/92.9%) and set thresholds a few
  points below actual so the gate has real headroom against normal
  churn without being able to silently regress back toward 85%: lines
  90%, functions 90%, branches 84%, statements 89%. `pnpm test:ci`
  verified green (exit 0) against the new thresholds.
- Documented in docs/testing.md, including the known-weak modules NOT
  touched this pass (convex/ai.ts, personas.ts, errors.ts, RoomChrome.tsx,
  the auth callback page) so they aren't lost as the next ratchet target.

No existing gate weakened; every number moved up.

Powder: linejam-911

Agent: linejam-overhaul
Agent-Surface: Claude Code
Agent-Model: anthropic/claude-fable-5
Agent-Task: linejam-911
Fresh-context critic review (per the lane bar's required pre-done pass)
found a real bug: checkAppHealth defaulted to http://localhost:3333, but
`pnpm dev` serves Next.js on :3000 (README) -- :3333 is deliberately
reserved for Playwright E2E specifically to avoid colliding with a running
dev server (playwright.config.ts). Every doctor.test.ts case mocked
fetchImpl directly, so nothing ever exercised the real default URL, and
following doctor's own printed instruction ("start it with `pnpm dev` and
re-run `pnpm doctor`") always produced a false "no app running" warning --
defeating linejam-909's entire stated purpose.

Live-verified the fix: started a real `next dev --turbopack -p 3000`,
confirmed :3000/api/health responds (200) while :3333 has nothing
listening, then ran `node scripts/doctor.mjs` against the real env and
got "app health: ok (http://localhost:3000/api/health)".

Also documented a second critic finding as a known limitation rather than
a fix: check-schema-migration-sequencing.mjs (linejam-914) only matches
`+` lines, so a migration pre-scaffolded in an earlier PR and filled in
alongside a schema contraction in a later PR would not trip it. Recorded
in docs/convex-migrations.md rather than over-building the heuristic --
it still catches the exact 2026-07-04 failure shape, which is its job.

Agent: linejam-overhaul
Agent-Surface: Claude Code
Agent-Model: anthropic/claude-fable-5
Agent-Task: linejam-909
@cursor

cursor Bot commented Jul 5, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_7986c372-92d3-43ea-9177-a635828d4e21)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 39-45: The Checkout code step in the workflow should disable
credential persistence because this job does not push. Update the
actions/checkout usage in the checkout step to set persist-credentials to false
alongside the existing fetch-depth setting, so the GITHUB_TOKEN is not left in
local git config and the zizmor artipacked warning is resolved.

In `@scripts/release/backfill-static-releases.mjs`:
- Around line 21-34: The version sorting logic in listVersionTags duplicates
compareVersions from static-release-store.mjs, so update listVersionTags to
reuse the imported compareVersions helper instead of reimplementing the
comparator. Keep the tag listing/filtering flow the same, but replace the inline
sort callback with compareVersions so backfill-static-releases.mjs stays
consistent with the shared version ordering logic.

In `@tests/app/join-page.test.tsx`:
- Around line 41-51: The test is over-mocking internal `@/lib/*` utilities
(`useUser`, `trackGameJoined`, `captureError`) instead of only mocking external
boundaries. Update `join-page.test.tsx` to let the real `@/lib/auth`,
`@/lib/analytics`, and `@/lib/error` implementations run, and keep mocks only
for true nondeterministic/system dependencies like `next/navigation` and
`convex/react`; use the real `errorToFeedback`/`captureError` path to exercise
the join flow end to end.

In `@tests/e2e/major-page-smoke.spec.ts`:
- Around line 16-20: The skip guard in the major-page smoke test is too strict
because it only checks GUEST_TOKEN_SECRET, which still skips remote runs even
when E2E_BASE_URL is provided. Update the condition around test.skip in
major-page-smoke.spec.ts to reflect the intended behavior: either skip only when
both GUEST_TOKEN_SECRET and E2E_BASE_URL are missing, or narrow the skip message
so it matches the current guard. Use the existing missingGuestTokenSecret setup
as the place to adjust the logic.
- Around line 105-106: The smoke test in `assertVisible`/page navigation is
using `page.goto(..., { waitUntil: 'networkidle' })`, which is brittle for pages
with ongoing background requests. Update the navigation call in
`tests/e2e/major-page-smoke.spec.ts` to use `domcontentloaded` instead, and rely
on `assertVisible(page)` to verify the page is ready.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 734dc300-75eb-4bcf-8096-eea8af95aeac

📥 Commits

Reviewing files that changed from the base of the PR and between 9e17782 and 65de13f.

📒 Files selected for processing (51)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • AGENTS.md
  • README.md
  • content/releases/manifest.json
  • content/releases/v1.0.0/changelog.json
  • content/releases/v1.1.0/changelog.json
  • content/releases/v1.1.1/changelog.json
  • content/releases/v1.1.2/changelog.json
  • content/releases/v1.1.3/changelog.json
  • content/releases/v1.10.0/changelog.json
  • content/releases/v1.11.0/changelog.json
  • content/releases/v1.12.0/changelog.json
  • content/releases/v1.13.0/changelog.json
  • content/releases/v1.14.0/changelog.json
  • content/releases/v1.15.0/changelog.json
  • content/releases/v1.15.1/changelog.json
  • content/releases/v1.2.0/changelog.json
  • content/releases/v1.3.0/changelog.json
  • content/releases/v1.4.0/changelog.json
  • content/releases/v1.5.0/changelog.json
  • content/releases/v1.6.0/changelog.json
  • content/releases/v1.7.0/changelog.json
  • content/releases/v1.8.0/changelog.json
  • content/releases/v1.9.0/changelog.json
  • content/releases/v1.9.1/changelog.json
  • docs/convex-migrations.md
  • docs/releases-static-store.md
  • docs/testing.md
  • lib/releases/index.ts
  • lib/releases/parser.ts
  • package.json
  • scripts/ci/check-schema-migration-sequencing.mjs
  • scripts/doctor.mjs
  • scripts/generate-releases.ts
  • scripts/lib/clerk-domain.mjs
  • scripts/release/backfill-static-releases.mjs
  • scripts/release/conventional-commits.mjs
  • scripts/release/static-release-store.mjs
  • scripts/release/write-release-from-git.mjs
  • scripts/setup.sh
  • tests/app/join-page.test.tsx
  • tests/e2e/major-page-smoke.spec.ts
  • tests/scripts/check-schema-migration-sequencing.test.ts
  • tests/scripts/clerk-domain.test.ts
  • tests/scripts/conventional-commits.test.ts
  • tests/scripts/doctor.test.ts
  • tests/scripts/release-manifest-version.test.ts
  • tests/scripts/static-release-store.test.ts
  • tests/scripts/write-release-from-git.test.ts
  • vitest.config.ts
💤 Files with no reviewable changes (2)
  • lib/releases/parser.ts
  • scripts/generate-releases.ts
✅ Files skipped from review due to trivial changes (25)
  • content/releases/v1.12.0/changelog.json
  • content/releases/v1.10.0/changelog.json
  • content/releases/v1.4.0/changelog.json
  • content/releases/v1.14.0/changelog.json
  • content/releases/v1.11.0/changelog.json
  • content/releases/v1.7.0/changelog.json
  • content/releases/v1.1.1/changelog.json
  • content/releases/v1.8.0/changelog.json
  • content/releases/v1.5.0/changelog.json
  • content/releases/v1.13.0/changelog.json
  • content/releases/v1.15.0/changelog.json
  • content/releases/v1.3.0/changelog.json
  • content/releases/v1.9.0/changelog.json
  • content/releases/v1.6.0/changelog.json
  • content/releases/v1.15.1/changelog.json
  • content/releases/manifest.json
  • content/releases/v1.2.0/changelog.json
  • content/releases/v1.1.2/changelog.json
  • docs/testing.md
  • content/releases/v1.9.1/changelog.json
  • content/releases/v1.0.0/changelog.json
  • content/releases/v1.1.3/changelog.json
  • content/releases/v1.1.0/changelog.json
  • docs/releases-static-store.md
  • README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/lib/clerk-domain.mjs

Comment thread .github/workflows/ci.yml
Comment on lines 39 to +45
- name: Checkout code
uses: actions/checkout@v7
with:
# linejam-914: the schema/migration sequencing check below needs
# full history to resolve a merge-base against the PR's base ref.
# This repo is small (a few hundred commits); a full fetch is cheap.
fetch-depth: 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Set persist-credentials: false on the checkout step.

This job never pushes; persisting the GITHUB_TOKEN in the local git config is unnecessary residual credential exposure. zizmor flags this as artipacked.

🔒 Proposed fix
       - name: Checkout code
         uses: actions/checkout@v7
         with:
           # linejam-914: the schema/migration sequencing check below needs
           # full history to resolve a merge-base against the PR's base ref.
           # This repo is small (a few hundred commits); a full fetch is cheap.
           fetch-depth: 0
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Checkout code
uses: actions/checkout@v7
with:
# linejam-914: the schema/migration sequencing check below needs
# full history to resolve a merge-base against the PR's base ref.
# This repo is small (a few hundred commits); a full fetch is cheap.
fetch-depth: 0
- name: Checkout code
uses: actions/checkout@v7
with:
# linejam-914: the schema/migration sequencing check below needs
# full history to resolve a merge-base against the PR's base ref.
# This repo is small (a few hundred commits); a full fetch is cheap.
fetch-depth: 0
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 39-45: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 39 - 45, The Checkout code step in the
workflow should disable credential persistence because this job does not push.
Update the actions/checkout usage in the checkout step to set
persist-credentials to false alongside the existing fetch-depth setting, so the
GITHUB_TOKEN is not left in local git config and the zizmor artipacked warning
is resolved.

Source: Linters/SAST tools

Comment on lines +21 to +34
function listVersionTags() {
return exec('git', ['tag', '--list', 'v1.*'])
.split('\n')
.filter(Boolean)
.sort((a, b) => {
const pa = a.replace(/^v/, '').split('.').map(Number);
const pb = b.replace(/^v/, '').split('.').map(Number);
for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
if (diff !== 0) return diff;
}
return 0;
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Reuse compareVersions instead of duplicating the sort logic.

This sort comparator (Lines 25-33) is identical to compareVersions already exported from static-release-store.mjs, which this file already imports from. Duplicating it risks the two diverging if one gets updated later.

♻️ Proposed fix
 import { writeReleaseFromGit } from './write-release-from-git.mjs';
-import { regenerateManifest } from './static-release-store.mjs';
+import { compareVersions, regenerateManifest } from './static-release-store.mjs';

 function exec(command, args) {
   return execFileSync(command, args, { encoding: 'utf8' }).trim();
 }

 function listVersionTags() {
   return exec('git', ['tag', '--list', 'v1.*'])
     .split('\n')
     .filter(Boolean)
-    .sort((a, b) => {
-      const pa = a.replace(/^v/, '').split('.').map(Number);
-      const pb = b.replace(/^v/, '').split('.').map(Number);
-      for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
-        const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
-        if (diff !== 0) return diff;
-      }
-      return 0;
-    });
+    .sort((a, b) =>
+      compareVersions(a.replace(/^v/, ''), b.replace(/^v/, ''))
+    );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function listVersionTags() {
return exec('git', ['tag', '--list', 'v1.*'])
.split('\n')
.filter(Boolean)
.sort((a, b) => {
const pa = a.replace(/^v/, '').split('.').map(Number);
const pb = b.replace(/^v/, '').split('.').map(Number);
for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
if (diff !== 0) return diff;
}
return 0;
});
}
function listVersionTags() {
return exec('git', ['tag', '--list', 'v1.*'])
.split('\n')
.filter(Boolean)
.sort((a, b) =>
compareVersions(a.replace(/^v/, ''), b.replace(/^v/, ''))
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/release/backfill-static-releases.mjs` around lines 21 - 34, The
version sorting logic in listVersionTags duplicates compareVersions from
static-release-store.mjs, so update listVersionTags to reuse the imported
compareVersions helper instead of reimplementing the comparator. Keep the tag
listing/filtering flow the same, but replace the inline sort callback with
compareVersions so backfill-static-releases.mjs stays consistent with the shared
version ordering logic.

Comment on lines +41 to +51
vi.mock('@/lib/auth', () => ({
useUser: () => mockUseUserReturn,
}));

vi.mock('@/lib/analytics', () => ({
trackGameJoined: (...args: unknown[]) => mockTrackGameJoined(...args),
}));

vi.mock('@/lib/error', () => ({
captureError: (...args: unknown[]) => mockCaptureError(...args),
}));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Internal @/lib/* modules are mocked, against tests guideline.

@/lib/auth, @/lib/analytics, and @/lib/error are internal domain utilities (per the lib/** guideline listing auth and error capture there), not system boundaries or sources of nondeterminism. The tests guideline says to mock only system boundaries/nondeterminism and not internal @/ modules or internal utilities. Consider letting these run for real (e.g. exercising real errorToFeedback/captureError logic) and only mocking true boundaries like next/navigation and convex/react, which are already handled correctly here.

Reasoning

Mocking internal utilities like captureError/trackGameJoined hides regressions in those modules and reduces confidence that the join flow's error/analytics wiring actually works end-to-end.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/app/join-page.test.tsx` around lines 41 - 51, The test is over-mocking
internal `@/lib/*` utilities (`useUser`, `trackGameJoined`, `captureError`)
instead of only mocking external boundaries. Update `join-page.test.tsx` to let
the real `@/lib/auth`, `@/lib/analytics`, and `@/lib/error` implementations run,
and keep mocks only for true nondeterministic/system dependencies like
`next/navigation` and `convex/react`; use the real
`errorToFeedback`/`captureError` path to exercise the join flow end to end.

Source: Path instructions

Comment on lines +16 to +20
const missingGuestTokenSecret = !process.env.GUEST_TOKEN_SECRET;
test.skip(
missingGuestTokenSecret,
'Set GUEST_TOKEN_SECRET for local E2E, or E2E_BASE_URL for a remote target'
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- file excerpt ---\n'
cat -n tests/e2e/major-page-smoke.spec.ts | sed -n '1,120p'

printf '\n--- env usage search ---\n'
rg -n "GUEST_TOKEN_SECRET|E2E_BASE_URL|test\.skip\(" tests/e2e tests -g '!**/node_modules/**'

Repository: misty-step/linejam

Length of output: 14401


Skip on either missing env, not just GUEST_TOKEN_SECRET. The guard still skips remote runs even when E2E_BASE_URL is set, so the condition should match the message or the message should be narrowed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/major-page-smoke.spec.ts` around lines 16 - 20, The skip guard in
the major-page smoke test is too strict because it only checks
GUEST_TOKEN_SECRET, which still skips remote runs even when E2E_BASE_URL is
provided. Update the condition around test.skip in major-page-smoke.spec.ts to
reflect the intended behavior: either skip only when both GUEST_TOKEN_SECRET and
E2E_BASE_URL are missing, or narrow the skip message so it matches the current
guard. Use the existing missingGuestTokenSecret setup as the place to adjust the
logic.

Comment on lines +105 to +106
await page.goto(path, { waitUntil: 'networkidle' });
await assertVisible(page);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== test file outline ==\n'
ast-grep outline tests/e2e/major-page-smoke.spec.ts --view expanded || true

printf '\n== relevant lines around the goto call ==\n'
nl -ba tests/e2e/major-page-smoke.spec.ts | sed -n '85,120p'

printf '\n== locate Convex / websocket usage in app routes used by this test ==\n'
rg -n "Convex|useQuery|useMutation|createClient|WebSocket|networkidle|domcontentloaded" tests src app pages components -g '!**/node_modules/**' || true

printf '\n== find assertVisible definition ==\n'
rg -n "function assertVisible|const assertVisible|async function assertVisible" tests/e2e -g '!**/node_modules/**' || true

Repository: misty-step/linejam

Length of output: 427


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== tests/e2e/major-page-smoke.spec.ts ==\n'
cat -n tests/e2e/major-page-smoke.spec.ts | sed -n '1,220p'

printf '\n== assertVisible definition(s) ==\n'
rg -n -A 40 -B 10 "assertVisible" tests/e2e -g '!**/node_modules/**' || true

printf '\n== app routes referenced by the smoke test ==\n'
python3 - <<'PY'
from pathlib import Path
import re

p = Path("tests/e2e/major-page-smoke.spec.ts")
text = p.read_text()
paths = sorted(set(re.findall(r"path:\s*['\"]([^'\"]+)['\"]", text)))
print("\n".join(paths))
PY

printf '\n== likely Convex/live data usage in app sources ==\n'
rg -n "useQuery|useMutation|Convex|createClient|websocket|WebSocket|subscribe" app src tests -g '!**/node_modules/**' || true

Repository: misty-step/linejam

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== app/providers.tsx ==\n'
cat -n app/providers.tsx | sed -n '1,220p'

printf '\n== route files for the smoke-test paths ==\n'
for f in \
  app/page.tsx \
  app/host/page.tsx \
  app/join/page.tsx \
  app/releases/page.tsx \
  app/me/poems/page.tsx \
  app/poem/[id]/page.tsx \
  app/recap/[code]/page.tsx
do
  if [ -f "$f" ]; then
    printf '\n--- %s ---\n' "$f"
    cat -n "$f" | sed -n '1,260p'
  fi
done

printf '\n== Playwright docs / local references to networkidle ==\n'
rg -n "networkidle|network idle|waitUntil" . -g '!**/node_modules/**' | sed -n '1,120p'

Repository: misty-step/linejam

Length of output: 32024


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== app/poem/[id]/PoemDetail.tsx ==\n'
cat -n app/poem/[id]/PoemDetail.tsx | sed -n '1,260p'

printf '\n== lib/auth.ts ==\n'
cat -n lib/auth.ts | sed -n '1,260p'

printf '\n== routes with useQuery / useMutation in app ==\n'
rg -n "useQuery|useMutation" app -g '!**/node_modules/**' | sed -n '1,160p'

printf '\n== any explicit websocket client setup ==\n'
rg -n "WebSocket|websocket|persistent.*connection|networkidle" app lib tests -g '!**/node_modules/**' | sed -n '1,200p'

Repository: misty-step/linejam

Length of output: 10997


🌐 Web query:

Playwright networkidle websockets documented wait forever persistent connections

💡 Result:

In Playwright, the 'networkidle' state is defined as the condition where there are no network connections for at least 500 milliseconds [1][2]. Because WebSockets, Server-Sent Events (SSE), and other persistent connections (such as analytics pings or health checks) often remain open indefinitely, they can prevent the 'networkidle' state from ever being reached [3][4][5]. Consequently, using 'networkidle' in pages with these types of connections will frequently cause tests to hang until they reach their configured timeout [6][3][7]. Playwright maintainers strongly discourage the use of 'networkidle' for production testing [1][5]. The official recommendation is to avoid "sniffing" the network state, as it is inherently unreliable in real-world applications [5]. Instead, users are advised to rely on web-first assertions and locators to verify that the application has reached the desired state [4][1][5]. For example, rather than waiting for the network to settle, you should wait for specific elements to become visible or contain expected text [5]: await page.locator('button').click; await expect(page.locator('status')).toHaveText('ready'); If you must wait for specific network activity, it is recommended to create custom utilities that track specific requests or responses rather than relying on the global 'networkidle' state [7][5][2]. Currently, Playwright does not provide a native option to ignore specific types of connections (like WebSockets) when calculating 'networkidle' [6][5][2].

Citations:


Avoid networkidle here tests/e2e/major-page-smoke.spec.ts:105-106

Several of these pages keep background activity alive via Convex and app-level providers, so waiting for global network quiescence is brittle and can stall CI. assertVisible already waits on the UI state; use domcontentloaded and let the assertions prove readiness.

♻️ Proposed fix
-        await page.goto(path, { waitUntil: 'networkidle' });
+        await page.goto(path, { waitUntil: 'domcontentloaded' });
         await assertVisible(page);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await page.goto(path, { waitUntil: 'networkidle' });
await assertVisible(page);
await page.goto(path, { waitUntil: 'domcontentloaded' });
await assertVisible(page);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/major-page-smoke.spec.ts` around lines 105 - 106, The smoke test in
`assertVisible`/page navigation is using `page.goto(..., { waitUntil:
'networkidle' })`, which is brittle for pages with ongoing background requests.
Update the navigation call in `tests/e2e/major-page-smoke.spec.ts` to use
`domcontentloaded` instead, and rely on `assertVisible(page)` to verify the page
is ready.

@moomooskycow

Copy link
Copy Markdown
Contributor Author

Closing this stale bundle without merging or deleting the donor branch.

Current master is 24 commits ahead, this PR is conflicting, its latest E2E/merge-gate checks are red, and its release slice is superseded by PRs #312/#319 plus the later 0.x reset. Wholesale merge would also delete the release generator that the current Pages path invokes.

Surviving donor map:

  • linejam-909: extract the doctor + port fix from 7ee876d / 65de13f; do not take release deletions.
  • linejam-910: reshape around runtime-error/fault-injection coverage; do not transplant the stale page matrix unchanged.
  • linejam-911: remeasure current master before any coverage ratchet; selected join-page tests may still be useful.
  • linejam-912: fold publishable-key CSP derivation into linejam-950 incident hardening.
  • linejam-913: keep the consecutive-failure idea, but replace the known-false TTL semantics tracked by linejam-030.
  • linejam-914: extract the expand–migrate–contract docs/gate plus hermetic fixture fix.

The commits remain available as donor evidence. New work should land as narrow, current-master PRs with fresh live proof.

Agent: orchestrator
Agent-Surface: Codex CLI
Agent-Task: linejam-952

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.

1 participant