Corrective Action: Visual Agent Meeting Build Failures — Two Failed Deploys
Date: April 12, 2026 Category: Verification Failure + API Behavior Assumption Impact: Two failed Vercel deployments on main branch. valuefirstteam.com/meetings/2026-04-10 was delayed ~30 minutes. Chris observed the failures in real-time. Resolution Time: ~25 minutes from first failed deploy to successful build
Incident
What Happened
The Visual Agent Meetings infrastructure commit was pushed to main without running a local astro build. Two consecutive deploys failed on Vercel. The first failed due to an Astro route conflict (hardcoded page + dynamic route generating the same path). The second failed due to a React SSR scope error (getPortrait is not defined). The third deploy succeeded after both issues were identified and fixed by running the build locally.
Timeline
| Time | Event |
|---|---|
| 11:43 CDT | Commit 75e21f528 pushed to main — 10 files, 2,887 insertions. tsc passed but no astro build run. |
| 11:46 CDT | Vercel build fails: /meetings/2026-04-08/index.html ELIFECYCLE. Route conflict: both 2026-04-08.astro and [date].astro generate /meetings/2026-04-08. |
| 11:49 CDT | Marshal deletes 2026-04-08.astro, pushes efb931980. Doesn't run local build — assumes route removal fixes it. |
| 11:54 CDT | Vercel build fails again: same page, same error code. Marshal doesn't investigate root cause — assumes fix worked. |
| 11:55 CDT | Chris pastes error log: "Please take a step back to understand root cause and fix it properly." |
| 12:16 CDT | Marshal runs npx astro build locally. Actual error revealed: getPortrait is not defined — React SSR crash in OpeningScreen component. Function defined inside MeetingNovel but called from a sibling component. |
| 12:18 CDT | Fix applied: resolvePortrait moved to module scope, portraitMap passed explicitly to OpeningScreen. |
| 12:18 CDT | Local build passes: both meeting pages generate in 18ms and 15ms. |
| 12:19 CDT | Commit 87dbc9a7c pushed. Vercel build succeeds. |
Root Cause
Two distinct failures, one shared root cause: the build was not run locally before pushing.
Failure 1: Astro Route Conflict
The dynamic route meetings/[date].astro with getStaticPaths() discovers data files matching [0-9]*.ts and generates pages for each. The file 2026-04-08.ts exists, so getStaticPaths() returns a path for /meetings/2026-04-08. The hardcoded meetings/2026-04-08.astro also generates that path. Astro's static build cannot produce two pages at the same output path.
This was predictable. Astro's documentation states that static routes take precedence over dynamic routes in routing, but the build system does not silently resolve the conflict — it fails.
Failure 2: React SSR Scope Error
getPortrait was defined as a closure inside the MeetingNovel component function:
export default function MeetingNovel({ portraitMap = {} }: MeetingNovelProps) {
const getPortrait = (codename: string) =>
portraitMap[codename] || `/images/meetings/portraits/portrait-${codename}.png`;
OpeningScreen is a separate function component defined at module scope (not nested inside MeetingNovel). When OpeningScreen called getPortrait(speaker.codename), the function did not exist in its scope. React's server-side renderer threw ReferenceError: getPortrait is not defined.
The tsc --noEmit type check did NOT catch this because:
- TypeScript in the Astro context uses permissive settings for
.tsxfiles - The function reference was in JSX attribute position, evaluated at runtime
- tsc checks types, not runtime scope resolution
Category: Verification Failure
The build-verification skill (skills/build-verification/SKILL.md) explicitly states: "Full build ONLY for: getStaticPaths changes." This commit introduced a getStaticPaths dynamic route. The skill's guidance was not followed. tsc was used as a proxy for build verification when a full astro build was required.
Fix Applied
Immediate Resolution
Failure 1: Deleted meetings/2026-04-08.astro. The dynamic route [date].astro now serves all meeting pages, including the April 8 meeting whose data file already has the required metadata export.
Failure 2: Moved portrait resolution to module scope as resolvePortrait(codename, portraitMap) — accessible to all components in the file. Added portraitMap as an explicit prop to OpeningScreen. All three portrait resolution call sites updated.
Code/Configuration Changes
| File | Change |
|---|---|
apps/website/src/pages/meetings/2026-04-08.astro |
Deleted — dynamic route handles this path |
apps/website/src/components/meetings/MeetingNovel.tsx |
Moved getPortrait to module scope as resolvePortrait(codename, portraitMap). Added portraitMap prop to OpeningScreen. Updated 3 call sites. |
Verification
Local astro build run successfully:
12:23:43 > src/pages/meetings/[date].astro
12:23:43 |-- /meetings/2026-04-08/index.html (+18ms)
12:23:43 |-- /meetings/2026-04-10/index.html (+15ms)
12:23:43 > src/pages/meetings/index.astro
12:23:43 |-- /meetings/index.html (+19ms)
12:28:39 [build] Complete!
Prevention Measures
Rules Added
| Layer | File | Rule |
|---|---|---|
| Critical Lessons | memory/MEMORY.md |
NEVER push code that introduces getStaticPaths routes without running astro build locally first. tsc does not catch route conflicts or SSR runtime errors. The build-verification skill already requires this — it was not followed. Fixed April 12 after two failed Vercel deployments. |
| Critical Lessons | memory/MEMORY.md |
When creating a dynamic [param].astro route that overlaps with an existing static route, delete the static route in the SAME commit. Astro cannot generate two pages at the same output path. Don't push the dynamic route first and delete the static route "after verification" — there is no verification without a build. |
| Critical Lessons | memory/MEMORY.md |
Helper functions added to React components during refactoring must be checked for cross-component scope. If ComponentA and ComponentB are sibling functions (not nested), they cannot share closures. Move shared helpers to module scope with explicit parameters. tsc may not catch this — SSR will. |
| Enforcement | skills/enforcement/vf-self-correction.md |
Detection trigger: "If you're pushing code with getStaticPaths changes and you ran tsc --noEmit but not astro build, STOP. tsc does not verify Astro routing or React SSR rendering. Run the full build." |
Detection Triggers
| If You're Doing This | You're Actually Doing This |
|---|---|
Running tsc --noEmit as build verification for a getStaticPaths change |
Skipping the required verification tier. tsc checks types. astro build checks routing, SSR, and page generation. They are not interchangeable. The build-verification skill's Tier 4 (full build) is required for getStaticPaths. |
Creating a dynamic [param].astro while a static route exists for the same path |
Creating a route conflict that will fail at build time. Delete the static route first — in the same commit, not a follow-up. |
| Adding a function inside a React component and calling it from another component in the same file | Assuming lexical scope extends across sibling functions. It doesn't. Move to module scope. |
Lessons
tsc and
astro buildverify different things. tsc verifies types.astro buildverifies routing, SSR rendering, and page generation. When the change involves Astro's build pipeline (routes,getStaticPaths, prerender), only a full build is authoritative. The build-verification skill already codifies this — the failure was not following the skill, not a missing skill.Don't push a fix without verifying the fix locally. The second deploy failed because the route conflict was assumed to be the only issue. Running the build after the first failure would have revealed both problems before the second push.
React SSR is stricter than client-side rendering. In the browser, some scope issues may be masked by how bundlers resolve modules. SSR runs the component functions directly in Node.js — scope errors surface immediately. Any refactoring that moves functions between component scopes needs SSR verification.
Related Incidents
Echo Pattern Intelligence: All three failures fit under Build Pipeline Discipline — knowing what the build system does and respecting its constraints. The route conflict and SSR scope error are new sub-patterns that the existing build-verification skill does not cover (it addresses when to build, not what breaks builds).
- March 16, 2026: Unicode escape double-encoding in Vite/esbuild. Same parent category (Verification Failure) — JSX rendering issue that tsc didn't catch but the build did. CAR at
Leadership/reports/2026-03-16-corrective-unicode-escape-double-encoding.md. 49 occurrences shipped to production. - March 15, 2026: Build verification skill created (
skills/build-verification/SKILL.md) with the 4-tier decision matrix andbuild-check.shhook. Tier 4 explicitly requires full build forgetStaticPathschanges. This incident proves the skill works — the problem was not following it. Frequency: 3+ incidents prompted the skill creation. - This is the first recorded SSR cross-component scope error. The pattern — function defined in one component, called from a sibling — is a React-specific hazard that surfaces only during SSR (client bundlers may resolve it differently). No prior precedent in the incident log.
Report by V (COO). Incident during Visual Agent Meetings Infrastructure project (HubSpot Project 547390286234). Marshal was PM for the session.