docs: add AI-agentic monorepo starter kit#2233
Conversation
Provide a copy-ready Vercel/GitHub AI-agentic monorepo starter with workflows, scaffold files, and Hypha role reference links so teams can bootstrap similar environments quickly. Co-authored-by: Cursor <cursoragent@cursor.com>
WalkthroughThis PR adds a security policy expansion for form submissions in the web middleware, creates a comprehensive agentic monorepo starter template with CI/CD workflows and example AI-powered chat application, and improves OneSignal initialization logic in the notifications subscriber to prevent concurrent initialization attempts. ChangesWeb App CSP Policy
Agentic Monorepo Starter Template
Notifications Subscriber Initialization Guard
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Prevent anonymous sessions from initializing OneSignal until a user is available and relax CSP form-action for Privy/Google auth submissions. This keeps login usable while avoiding unauth startup errors. Co-authored-by: Cursor <cursoragent@cursor.com>
|
🔗 Custom preview URL: https://pr-2233.preview-app.hypha.earth |
There was a problem hiding this comment.
Actionable comments posted: 20
🤖 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 `@apps/web/src/middleware.ts`:
- Line 77: Remove the overly permissive wildcard from the CSP form-action entry:
locate the header string "form-action 'self' https://auth.privy.io
https://privy.hypha.earth https://accounts.google.com https://*.google.com" in
apps/web/src/middleware.ts and delete the "https://*.google.com" token so only
the explicitly required origins (e.g., https://accounts.google.com) remain; if
any additional specific Google subdomain is truly needed for the auth flow, add
that exact origin instead of a wildcard.
In `@docs/agentic-monorepo-starter/.github/pull_request_template.md`:
- Around line 1-14: Add a top-level heading and ensure blank lines around each
section heading to satisfy MD041/MD022; specifically insert a single H1 title
line above the existing content (e.g., "# Pull Request") and add an empty line
before and after each "## Change", "## Reason", "## Validation", and "## Risk /
Rollback" heading so the template has proper top-level heading and spacing for
the Markdown linter.
In `@docs/agentic-monorepo-starter/.github/workflows/deploy-web.yml`:
- Line 23: Replace the unpinned, npm-based install step that runs "npm i -g
vercel@latest" with a pinned pnpm install to avoid mixing package managers and
accidental breaking upgrades; specifically change the workflow step that
contains the run command "npm i -g vercel@latest" to use pnpm global install
with a fixed version (e.g., "pnpm add -g vercel@<major.minor.patch>") or use a
pinned CLI invocation pattern that matches the repo's pnpm usage.
- Around line 25-39: The workflow is missing VERCEL_ORG_ID and VERCEL_PROJECT_ID
and is pulling at repo root while building in apps/web, so update the GitHub
Actions steps to export/use the VERCEL_ORG_ID and VERCEL_PROJECT_ID secrets
alongside VERCEL_TOKEN and run vercel pull from the same cwd as the build;
specifically ensure the "Pull Vercel env" step passes VERCEL_ORG_ID and
VERCEL_PROJECT_ID (from secrets) into the environment and invoke vercel pull
with the same --cwd apps/web flag (or run both vercel pull and vercel build from
apps/web) so .vercel/project.json is created where vercel build --cwd apps/web
can find it.
- Around line 9-47: The deploy job currently runs independently and without a
timeout; update the deploy job (job id: deploy) to depend on the CI job by
adding a needs: ["ci"] (or the exact job id used in your CI workflow) so it only
runs after CI succeeds, and add timeout-minutes: 30 (or your desired limit) to
the deploy job to prevent runaway runs; ensure the change is applied at the job
level (next to runs-on) that contains the steps like "Pull Vercel env", "Build
web", and "Deploy web".
In `@docs/agentic-monorepo-starter/apps/web/.eslintrc.json`:
- Around line 1-3: The repo currently uses legacy .eslintrc.json which can
surprise users on eslint@^9.17.0; replace the legacy config by adding an
eslint.config.mjs that imports/exports the Next.js flat config (via
eslint-config-next's flat export) and any project rules, or alternatively
document/keep .eslintrc.json but set ES LINT_USE_FLAT_CONFIG=false; update
references to use eslint.config.mjs and ensure the flat-config export includes
the same extends/rules as the previous .eslintrc.json and any overrides from
"eslint-config-next".
In `@docs/agentic-monorepo-starter/apps/web/app/api/chat/route.ts`:
- Around line 4-16: The POST handler is currently unauthenticated and calls
streamText directly, exposing paid API usage; add an auth/session guard at the
top of the POST function (validate an API key or session token from req.headers
or use your session helper) and return a 401/403 before calling streamText if
the check fails. Specifically, in the POST function that currently calls
streamText (and uses convertToModelMessages and toUIMessageStreamResponse),
verify the caller (e.g., check Authorization header or getServerSession) and
optionally enforce rate-limiting/quota, then only call streamText when
authentication succeeds.
- Line 5: Wrap the await req.json() call and the destructuring const { messages
}: { messages: UIMessage[] } = await req.json() in a try/catch inside the POST
handler (or whichever request handler contains that line), catch SyntaxError and
other parsing errors, and return a structured 400 response (e.g.,
NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })); also validate
that messages exists and is an array of UIMessage (return 400 with a clear
message if not) before proceeding.
- Around line 7-13: The code uses model identifier "openai/gpt-5.4" in
streamText(...) which requires AI SDK v5.0.36+ but package.json pins "ai":
"^4.0.0"; fix by updating the "ai" dependency in package.json to at least
"^5.0.36" (or "^6.0.0"/latest stable) so the creator/model-name syntax is
supported, or alternatively change the streamText call to use a provider
instance compatible with v4 (e.g., use the `@ai-sdk/openai` provider like
openai('gpt-4') passed to streamText and avoid the "openai/gpt-5.4" string) and
ensure any related imports/configs are adjusted accordingly.
In `@docs/agentic-monorepo-starter/apps/web/app/globals.css`:
- Line 9: The font-family declaration uses an unquoted Segoe UI which triggers
Stylelint's font-family-name-quotes rule; update the font-family line (the
font-family declaration) to wrap Segoe UI in quotes (e.g., "Segoe UI") so the
rule passes, and run the linter to confirm no further font-family-name-quotes
violations remain.
In `@docs/agentic-monorepo-starter/apps/web/app/page.tsx`:
- Around line 35-40: The input currently uses only a placeholder which is not
accessible; add a programmatic label for the chat input by assigning an id to
the input (e.g., id="chat-input") and either render an associated <label
htmlFor="chat-input">Chat message</label> (visually hide it with your existing
"sr-only" class if you don't want it visible) or add an aria-label="Chat
message" to the same input; update the JSX where value={text} and
onChange={(e)=>setText(e.target.value)} to include that id/aria-label so screen
readers can announce the field.
- Line 22: The map over m.parts uses the unstable array index (m.parts.map((p,
i) => ... key={i})), which causes remounts during streaming; replace the index
key with a stable identifier such as p.id or a composite like `${m.id}-${p.id}`
(or, if parts currently lack an id, attach a stable id when parts are created)
so the JSX inside m.parts.map uses key={p.id} or key={`${m.id}-${p.id}`} instead
of key={i} to ensure proper React reconciliation.
- Around line 9-11: The DefaultChatTransport instance is being recreated every
render causing useChat to reinitialize; update the component to memoize the
transport passed to useChat (e.g., create a single DefaultChatTransport and
reuse it) so the transport object identity is stable; locate the useChat call
and the inline new DefaultChatTransport({...}) and replace that inline
construction with a memoized value (useMemo or module-level singleton) so
messages, sendMessage, and status aren't reset by transport re-instantiation.
- Around line 4-5: The project imports and uses APIs (DefaultChatTransport,
sendMessage, the parts-based message shape and status values
"submitted"/"streaming", plus route.ts symbols convertToModelMessages,
stepCountIs, UIMessage) that exist only in AI SDK 6.x and `@ai-sdk/react` 3.x, but
package.json pins "ai": "^4.0.0" and "@ai-sdk/react": "^1.0.0"; update
package.json to use the compatible versions (upgrade "ai" to a 6.x release and
"@ai-sdk/react" to a 3.x release) so those symbols resolve and
runtime/installation succeed, then run install and smoke-test
useChat/DefaultChatTransport flows to confirm compatibility.
In `@docs/agentic-monorepo-starter/apps/web/next.config.ts`:
- Line 3: The Next config currently defines nextConfig without telling Next to
transpile workspace packages, so imports from the workspace package `@repo/agents`
(used by app/api/chat/route.ts) will fail; update the nextConfig object to
include a transpilePackages array that contains '@repo/agents' (i.e., add
transpilePackages: ['@repo/agents']) and keep exporting nextConfig so Next.js
will transpile that package's TypeScript source during build.
In `@docs/agentic-monorepo-starter/apps/web/package.json`:
- Around line 14-16: The project depends on "ai": "^4.0.0" which does not
provide the v5+ exports used (stopWhen, inputSchema, UIMessage,
convertToModelMessages) and causes build failures; update the package.json
dependency entry for "ai": "^4.0.0" to "ai": "^6.0.0" (or at minimum "^5.0.0")
so those APIs are available, then reinstall/update the lockfile (npm/yarn/pnpm
install) to ensure the new version is installed.
In `@docs/agentic-monorepo-starter/packages/agents/package.json`:
- Line 9: Package.json currently pins the "ai" dependency to "^4.0.0" which is
incompatible with the code in src/index.ts that uses v5/v6 APIs (e.g.,
inputSchema: z.object(...)); update the version constraint for the "ai"
dependency in packages/agents/package.json from "^4.0.0" to "^6.0.0" so the
installed package matches the inputSchema API used by the tool definitions and
restore runtime compatibility.
In `@docs/agentic-monorepo-starter/packages/agents/src/index.ts`:
- Around line 10-12: The execute handler currently calls new
Date().toLocaleString("en-US", { timeZone: timezone }) which will throw a
RangeError for invalid IANA zone names; update the execute function to validate
or safely handle invalid timezones by either wrapping the date conversion in a
try/catch and returning a sensible fallback (e.g., UTC or an error field) or by
checking timezone against an allowed IANA list before calling toLocaleString;
refer to the execute function and the timezone/now variables to implement the
validation or try/catch and ensure the returned object always contains a stable
now value and clear indication when the provided timezone was invalid.
In `@docs/agentic-monorepo-starter/README.md`:
- Around line 13-22: In docs/agentic-monorepo-starter/README.md the fenced code
block after list item "2." breaks the numbered list (MD029); fix by indenting
the entire code fence and its contents three spaces so the opening ```bash, its
lines ("pnpm install", "pnpm dev") and the closing ``` are aligned under list
item 2, keeping items "3. Open <http://localhost:3000>." and "4. Ask: ..." as
the continued numbered list.
In `@packages/notifications/src/components/notifications-subscriber.tsx`:
- Around line 159-166: The initialization effect's early-exit guard
(initialize() checking initialized and hasInitAttemptRef.current) plus having
initialized in its dependency array causes listeners registered during
initialize() to be removed on re-render and never re-registered; fix by moving
all OneSignal event listener registration/removal out of initialize() into a
separate effect that watches only initialized (e.g., create a new useEffect that
subscribes to OneSignal.on('notificationDisplay', ...),
OneSignal.on('notificationClick', ...), onPermissionChange,
onSubscriptionChange, etc., and returns a cleanup to remove them), remove
initialized from the dependency array of the existing initialization effect that
calls initialize() and keep the hasInitAttemptRef/slug/appId guards there so
initialize() only handles OneSignal.init() and setInitialized(true).
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5af35268-35db-45e0-89b1-46e217a6cd94
📒 Files selected for processing (27)
apps/web/src/middleware.tsdocs/agentic-monorepo-starter/.github/pull_request_template.mddocs/agentic-monorepo-starter/.github/workflows/ci.ymldocs/agentic-monorepo-starter/.github/workflows/deploy-web.ymldocs/agentic-monorepo-starter/.gitignoredocs/agentic-monorepo-starter/AGENTS.mddocs/agentic-monorepo-starter/README.mddocs/agentic-monorepo-starter/apps/web/.eslintrc.jsondocs/agentic-monorepo-starter/apps/web/app/api/chat/route.tsdocs/agentic-monorepo-starter/apps/web/app/globals.cssdocs/agentic-monorepo-starter/apps/web/app/layout.tsxdocs/agentic-monorepo-starter/apps/web/app/page.tsxdocs/agentic-monorepo-starter/apps/web/next-env.d.tsdocs/agentic-monorepo-starter/apps/web/next.config.tsdocs/agentic-monorepo-starter/apps/web/package.jsondocs/agentic-monorepo-starter/apps/web/tsconfig.jsondocs/agentic-monorepo-starter/package.jsondocs/agentic-monorepo-starter/packages/agents/package.jsondocs/agentic-monorepo-starter/packages/agents/src/index.tsdocs/agentic-monorepo-starter/packages/config/README.mddocs/agentic-monorepo-starter/packages/eslint-config/next.jsdocs/agentic-monorepo-starter/packages/eslint-config/package.jsondocs/agentic-monorepo-starter/packages/tsconfig/base.jsondocs/agentic-monorepo-starter/packages/tsconfig/package.jsondocs/agentic-monorepo-starter/pnpm-workspace.yamldocs/agentic-monorepo-starter/turbo.jsonpackages/notifications/src/components/notifications-subscriber.tsx
| "object-src 'none'", | ||
| "base-uri 'self'", | ||
| "form-action 'self'", | ||
| "form-action 'self' https://auth.privy.io https://privy.hypha.earth https://accounts.google.com https://*.google.com", |
There was a problem hiding this comment.
https://*.google.com wildcard in form-action is overly permissive
accounts.google.com is already enumerated explicitly in the same directive, so the wildcard is redundant for its stated purpose and unnecessarily broadens the CSP attack surface to every *.google.com subdomain (e.g., docs.google.com, mail.google.com, and any future or obscure Google subdomain). If any of those has an open redirect or subdomain takeover, an injected form could exfiltrate data there while still satisfying this CSP directive.
Remove the wildcard and enumerate only the specific origins that are actually needed:
🔧 Proposed fix
- "form-action 'self' https://auth.privy.io https://privy.hypha.earth https://accounts.google.com https://*.google.com",
+ "form-action 'self' https://auth.privy.io https://privy.hypha.earth https://accounts.google.com",If another specific *.google.com subdomain is genuinely required for the auth flow, add it explicitly rather than using the wildcard.
📝 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.
| "form-action 'self' https://auth.privy.io https://privy.hypha.earth https://accounts.google.com https://*.google.com", | |
| "form-action 'self' https://auth.privy.io https://privy.hypha.earth https://accounts.google.com", |
🤖 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 `@apps/web/src/middleware.ts` at line 77, Remove the overly permissive wildcard
from the CSP form-action entry: locate the header string "form-action 'self'
https://auth.privy.io https://privy.hypha.earth https://accounts.google.com
https://*.google.com" in apps/web/src/middleware.ts and delete the
"https://*.google.com" token so only the explicitly required origins (e.g.,
https://accounts.google.com) remain; if any additional specific Google subdomain
is truly needed for the auth flow, add that exact origin instead of a wildcard.
| ## Change | ||
| - | ||
|
|
||
| ## Reason | ||
| - | ||
|
|
||
| ## Validation | ||
| - [ ] lint | ||
| - [ ] typecheck | ||
| - [ ] tests | ||
| - [ ] manual check | ||
|
|
||
| ## Risk / Rollback | ||
| - |
There was a problem hiding this comment.
Normalize heading structure to pass Markdown lint checks
Current heading layout triggers MD041/MD022. Add a top-level heading and blank lines around section headings.
Suggested fix
+# Pull Request
+
## Change
+
-
## Reason
+
-
## Validation
+
- [ ] lint
- [ ] typecheck
- [ ] tests
- [ ] manual check
## Risk / Rollback
+
-📝 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.
| ## Change | |
| - | |
| ## Reason | |
| - | |
| ## Validation | |
| - [ ] lint | |
| - [ ] typecheck | |
| - [ ] tests | |
| - [ ] manual check | |
| ## Risk / Rollback | |
| - | |
| # Pull Request | |
| ## Change | |
| - | |
| ## Reason | |
| - | |
| ## Validation | |
| - [ ] lint | |
| - [ ] typecheck | |
| - [ ] tests | |
| - [ ] manual check | |
| ## Risk / Rollback | |
| - |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 1-1: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
[warning] 4-4: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 7-7: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 13-13: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🤖 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/agentic-monorepo-starter/.github/pull_request_template.md` around lines
1 - 14, Add a top-level heading and ensure blank lines around each section
heading to satisfy MD041/MD022; specifically insert a single H1 title line above
the existing content (e.g., "# Pull Request") and add an empty line before and
after each "## Change", "## Reason", "## Validation", and "## Risk / Rollback"
heading so the template has proper top-level heading and spacing for the
Markdown linter.
| jobs: | ||
| deploy: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: pnpm/action-setup@v4 | ||
| with: | ||
| version: 9 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 24 | ||
| cache: pnpm | ||
|
|
||
| - run: pnpm install --frozen-lockfile | ||
| - run: npm i -g vercel@latest | ||
|
|
||
| - name: Pull Vercel env | ||
| run: | | ||
| if [ "${{ github.event_name }}" = "pull_request" ]; then | ||
| vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} | ||
| else | ||
| vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} | ||
| fi | ||
|
|
||
| - name: Build web | ||
| run: | | ||
| if [ "${{ github.event_name }}" = "pull_request" ]; then | ||
| vercel build --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | ||
| else | ||
| vercel build --prod --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | ||
| fi | ||
|
|
||
| - name: Deploy web | ||
| run: | | ||
| if [ "${{ github.event_name }}" = "pull_request" ]; then | ||
| vercel deploy --prebuilt --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | ||
| else | ||
| vercel deploy --prebuilt --prod --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | ||
| fi |
There was a problem hiding this comment.
Deploy job runs in parallel with CI — broken code can ship to production — and has no timeout.
Both ci.yml and deploy-web.yml trigger on the same events with no ordering constraint. If CI fails (lint, typecheck, build) the deploy job still proceeds concurrently. Add a needs reference to the CI job, or merge the deploy step into ci.yml after all validation steps. Also add a timeout-minutes to prevent runaway deployments from hanging the runner indefinitely.
🔧 Proposed fix (separate workflow approach)
jobs:
deploy:
runs-on: ubuntu-latest
+ timeout-minutes: 20
+ needs: ci # reference the CI job name from ci.yml if using reusable workflows,
+ # or move this job into ci.yml after the build step🤖 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/agentic-monorepo-starter/.github/workflows/deploy-web.yml` around lines
9 - 47, The deploy job currently runs independently and without a timeout;
update the deploy job (job id: deploy) to depend on the CI job by adding a
needs: ["ci"] (or the exact job id used in your CI workflow) so it only runs
after CI succeeds, and add timeout-minutes: 30 (or your desired limit) to the
deploy job to prevent runaway runs; ensure the change is applied at the job
level (next to runs-on) that contains the steps like "Pull Vercel env", "Build
web", and "Deploy web".
| cache: pnpm | ||
|
|
||
| - run: pnpm install --frozen-lockfile | ||
| - run: npm i -g vercel@latest |
There was a problem hiding this comment.
npm i -g vercel@latest is unpinned and mixes package managers.
The project uses pnpm, but this step installs via npm. @latest is also unpinned — a new Vercel CLI major release could silently break the workflow. Pin to a specific version and use pnpm consistently:
🔧 Proposed fix
- - run: npm i -g vercel@latest
+ - run: pnpm add -g vercel@39 # pin to the latest known-good major📝 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.
| - run: npm i -g vercel@latest | |
| - run: pnpm add -g vercel@39 # pin to the latest known-good major |
🤖 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/agentic-monorepo-starter/.github/workflows/deploy-web.yml` at line 23,
Replace the unpinned, npm-based install step that runs "npm i -g vercel@latest"
with a pinned pnpm install to avoid mixing package managers and accidental
breaking upgrades; specifically change the workflow step that contains the run
command "npm i -g vercel@latest" to use pnpm global install with a fixed version
(e.g., "pnpm add -g vercel@<major.minor.patch>") or use a pinned CLI invocation
pattern that matches the repo's pnpm usage.
| - name: Pull Vercel env | ||
| run: | | ||
| if [ "${{ github.event_name }}" = "pull_request" ]; then | ||
| vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} | ||
| else | ||
| vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} | ||
| fi | ||
|
|
||
| - name: Build web | ||
| run: | | ||
| if [ "${{ github.event_name }}" = "pull_request" ]; then | ||
| vercel build --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | ||
| else | ||
| vercel build --prod --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | ||
| fi |
There was a problem hiding this comment.
vercel pull creates .vercel/ in the repo root but vercel build --cwd apps/web looks for it in apps/web/.vercel/ — and both are missing VERCEL_ORG_ID/VERCEL_PROJECT_ID.
Two related problems:
-
Missing env vars: The Vercel CLI needs
VERCEL_ORG_IDandVERCEL_PROJECT_IDto know which project to operate on. These are typically set as GitHub repository secrets alongsideVERCEL_TOKEN, but they are absent from the workflow. Without themvercel pullwill either prompt interactively (hanging the runner) or fail. -
Working-directory mismatch:
vercel pullis run from the repo root, placing.vercel/project.jsonthere.vercel build --cwd apps/webexecutes relative toapps/web/and looks for.vercel/inside that directory — a different location. Add--cwd apps/webto thevercel pullstep as well, or consistently run all Vercel commands fromapps/web.
🔧 Proposed fix
+ env:
+ VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
+ VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
- name: Pull Vercel env
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
- vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
+ vercel pull --yes --environment=preview --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }}
else
- vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
+ vercel pull --yes --environment=production --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }}
fi📝 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.
| - name: Pull Vercel env | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} | |
| else | |
| vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} | |
| fi | |
| - name: Build web | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| vercel build --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | |
| else | |
| vercel build --prod --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | |
| fi | |
| - name: Pull Vercel env | |
| env: | |
| VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} | |
| VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| vercel pull --yes --environment=preview --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | |
| else | |
| vercel pull --yes --environment=production --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | |
| fi | |
| - name: Build web | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| vercel build --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | |
| else | |
| vercel build --prod --cwd apps/web --token=${{ secrets.VERCEL_TOKEN }} | |
| fi |
🤖 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/agentic-monorepo-starter/.github/workflows/deploy-web.yml` around lines
25 - 39, The workflow is missing VERCEL_ORG_ID and VERCEL_PROJECT_ID and is
pulling at repo root while building in apps/web, so update the GitHub Actions
steps to export/use the VERCEL_ORG_ID and VERCEL_PROJECT_ID secrets alongside
VERCEL_TOKEN and run vercel pull from the same cwd as the build; specifically
ensure the "Pull Vercel env" step passes VERCEL_ORG_ID and VERCEL_PROJECT_ID
(from secrets) into the environment and invoke vercel pull with the same --cwd
apps/web flag (or run both vercel pull and vercel build from apps/web) so
.vercel/project.json is created where vercel build --cwd apps/web can find it.
| "@ai-sdk/react": "^1.0.0", | ||
| "@repo/agents": "workspace:*", | ||
| "ai": "^4.0.0", |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the latest stable version of the "ai" npm package (Vercel AI SDK v5)?
💡 Result:
Latest stable version of the Vercel AI SDK “ai” npm package (package name: ai) as of 2026-05-06: 6.0.175 [1][2]. Source evidence: npm’s version history for the ai package shows 6.0.175 published May 4, 2026, and the package page shows the currently viewed/installed version as 6.0.175 [1][2].
Citations:
🏁 Script executed:
# First, find the route.ts and agents/index.ts files to check if they actually use the claimed v5 APIs
find . -name "route.ts" -o -name "agents" -type dRepository: hypha-dao/hypha-web
Length of output: 2409
🏁 Script executed:
# Check the chat route.ts file for API usage
cat -n docs/agentic-monorepo-starter/apps/web/app/api/chat/route.ts
# Check for agents index file
find ./docs/agentic-monorepo-starter/packages/agents -name "index.ts" -o -name "index.js" | head -5Repository: hypha-dao/hypha-web
Length of output: 746
"ai": "^4.0.0" will not install AI SDK v5+ — the template is non-functional as shipped.
The code uses four v5-only APIs from the "ai" package: stopWhen, inputSchema, UIMessage, and convertToModelMessages (confirmed in docs/agentic-monorepo-starter/apps/web/app/api/chat/route.ts). The semver range ^4.0.0 resolves to ai@4.x and will not include these exports, causing compilation failure.
Update to "^6.0.0" to match the latest stable version (6.0.175 as of May 2026). At minimum, "^5.0.0" is required for the v5 APIs, but v6 is the current stable release.
🔧 Proposed fix
- "ai": "^4.0.0",
+ "ai": "^6.0.0",📝 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.
| "@ai-sdk/react": "^1.0.0", | |
| "@repo/agents": "workspace:*", | |
| "ai": "^4.0.0", | |
| "@ai-sdk/react": "^1.0.0", | |
| "@repo/agents": "workspace:*", | |
| "ai": "^6.0.0", |
🤖 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/agentic-monorepo-starter/apps/web/package.json` around lines 14 - 16,
The project depends on "ai": "^4.0.0" which does not provide the v5+ exports
used (stopWhen, inputSchema, UIMessage, convertToModelMessages) and causes build
failures; update the package.json dependency entry for "ai": "^4.0.0" to "ai":
"^6.0.0" (or at minimum "^5.0.0") so those APIs are available, then
reinstall/update the lockfile (npm/yarn/pnpm install) to ensure the new version
is installed.
| "main": "src/index.ts", | ||
| "types": "src/index.ts", | ||
| "dependencies": { | ||
| "ai": "^4.0.0", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check the actual package.json file content
cat -n docs/agentic-monorepo-starter/packages/agents/package.jsonRepository: hypha-dao/hypha-web
Length of output: 356
🏁 Script executed:
# Check if src/index.ts exists and examine its contents
find docs/agentic-monorepo-starter/packages/agents -name "index.ts" -o -name "index.js"Repository: hypha-dao/hypha-web
Length of output: 122
🌐 Web query:
ai package npm latest version Vercel AI SDK current release
💡 Result:
Latest npm version of the Vercel AI SDK package "ai" (i.e., install command: npm i ai) is 6.0.175 (published May 4, 2026) [1][2]. How to install: npm i ai This corresponds to the GitHub release list showing ai@6.0.175 among recent releases [3].
Citations:
- 1: https://www.npmjs.com/package/ai?activeTab=versions
- 2: https://www.npmjs.com/package/ai
- 3: https://github.com/vercel/ai/releases
🌐 Web query:
ai package npm version 6 breaking changes from v4 v5
💡 Result:
The npm package "ai" (Vercel AI SDK) is currently at version 6.0.175 as of May 4, 2026 [1][2]. It has official migration guides detailing breaking changes. Breaking changes from v4 to v5 (major overhaul) [3][4]: - UIMessage and type system changes, including tool definitions (parameters → inputSchema), tool properties (args/result → input/output). - Stream data removal; custom streaming changes (writeMessageAnnotation/writeData removed). - Tool call streaming now default. - UI hooks moved (e.g., React to @ai-sdk/react); useChat changes like removed managed input state, onResponse removal, addToolResult → addToolOutput. - Run codemod: npx @ai-sdk/codemod v5 [5][3][6]. - Data migration needed for stored messages [7]. Breaking changes from v5 to v6 [8][9][10][11]: - CoreMessage removed → ModelMessage; convertToCoreMessages → convertToModelMessages (now async, add await). - textEmbeddingModel/embedding → embeddingModel/embedding. - ToolCallOptions → ToolExecutionOptions. - Mock classes V2 → V3 in ai/test. - Provider-specific: @ai-sdk/azure uses Responses API (use.chat for old); @ai-sdk/openai treats unknown models as reasoning. - generateObject/streamObject deprecated. - Run codemod: npx @ai-sdk/codemod v6 [8][10]. For full details and codemods covering most renames/refactors, see official docs [3][8][12]. Upgrade all related packages (ai, @ai-sdk/*) together [3][8].
Citations:
- 1: https://www.npmjs.com/package/ai?activeTab=versions
- 2: https://www.npmjs.com/package/ai
- 3: https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0
- 4: https://v4.ai-sdk.dev/docs/migration-guides/migration-guide-5-0
- 5: https://github.com/vercel/ai/compare/ai@4.3.16...ai@5.0.5
- 6: https://vercel.com/blog/ai-sdk-5
- 7: https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0-data
- 8: https://ai-sdk.dev/v7/docs/migration-guides/migration-guide-6-0
- 9: https://ai-sdk.dev/v5/docs/migration-guides/migration-guide-6-0
- 10: https://www.npmjs.com/package/@ai-sdk/codemod
- 11: https://sdk.vercel.ai/docs/migration-guides/migration-guide-6-0
- 12: https://ai-sdk.dev/docs/migration-guides
🏁 Script executed:
cat -n docs/agentic-monorepo-starter/packages/agents/src/index.tsRepository: hypha-dao/hypha-web
Length of output: 555
Bump ai to ^6.0.0 — current version ^4.0.0 is incompatible with the starter code
The ai package is pinned to ^4.0.0 but the src/index.ts tool definitions already use v5/v6 API signatures. Specifically, the code uses inputSchema: z.object(...) which is the v5+ syntax; v4 uses parameters: z.object(...) instead. This incompatibility means the starter template will fail at runtime.
Bump to ^6.0.0 (current stable, v6.0.175 as of May 4, 2026) to align dependency with actual code requirements. Breaking changes exist between v4→v5→v6, but since the code is already written for v5/v6 APIs, the only action needed is updating the version constraint.
🔧 Proposed fix
- "ai": "^4.0.0",
+ "ai": "^6.0.0",📝 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.
| "ai": "^4.0.0", | |
| "ai": "^6.0.0", |
🤖 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/agentic-monorepo-starter/packages/agents/package.json` at line 9,
Package.json currently pins the "ai" dependency to "^4.0.0" which is
incompatible with the code in src/index.ts that uses v5/v6 APIs (e.g.,
inputSchema: z.object(...)); update the version constraint for the "ai"
dependency in packages/agents/package.json from "^4.0.0" to "^6.0.0" so the
installed package matches the inputSchema API used by the tool definitions and
restore runtime compatibility.
| execute: async ({ timezone }) => { | ||
| const now = new Date().toLocaleString("en-US", { timeZone: timezone }); | ||
| return { timezone, now }; |
There was a problem hiding this comment.
Unvalidated timezone string will throw RangeError for invalid IANA zone names.
new Date().toLocaleString("en-US", { timeZone: timezone }) throws a RangeError when timezone is not a valid IANA timezone identifier (e.g. "foo", "UTC+5"). Since the AI model supplies this argument, it may occasionally produce invalid values. Wrap the body in a try/catch or validate with a known-valid zone list.
🛡️ Proposed fix
execute: async ({ timezone }) => {
- const now = new Date().toLocaleString("en-US", { timeZone: timezone });
- return { timezone, now };
+ try {
+ const now = new Date().toLocaleString("en-US", { timeZone: timezone });
+ return { timezone, now };
+ } catch {
+ return { error: `Invalid timezone: "${timezone}". Please use a valid IANA timezone name such as "America/New_York".` };
+ }
}📝 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.
| execute: async ({ timezone }) => { | |
| const now = new Date().toLocaleString("en-US", { timeZone: timezone }); | |
| return { timezone, now }; | |
| execute: async ({ timezone }) => { | |
| try { | |
| const now = new Date().toLocaleString("en-US", { timeZone: timezone }); | |
| return { timezone, now }; | |
| } catch { | |
| return { error: `Invalid timezone: "${timezone}". Please use a valid IANA timezone name such as "America/New_York".` }; | |
| } | |
| } |
🤖 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/agentic-monorepo-starter/packages/agents/src/index.ts` around lines 10 -
12, The execute handler currently calls new Date().toLocaleString("en-US", {
timeZone: timezone }) which will throw a RangeError for invalid IANA zone names;
update the execute function to validate or safely handle invalid timezones by
either wrapping the date conversion in a try/catch and returning a sensible
fallback (e.g., UTC or an error field) or by checking timezone against an
allowed IANA list before calling toLocaleString; refer to the execute function
and the timezone/now variables to implement the validation or try/catch and
ensure the returned object always contains a stable now value and clear
indication when the provided timezone was invalid.
| 1. Copy this folder to a new repo root. | ||
| 2. Run install and development: | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| pnpm dev | ||
| ``` | ||
|
|
||
| 3. Open <http://localhost:3000>. | ||
| 4. Ask: "What time is it in Europe/Amsterdam?" |
There was a problem hiding this comment.
Ordered list numbering breaks after the code block (MD029)
The fenced code block between items 2 and 3 is not indented under the list item, so most parsers and linters treat items 3 and 4 as a new list that unexpectedly starts at 3. markdownlint flags this as MD029.
The simplest fix is to indent the code block (3 spaces, to align with the list item content) so it stays part of item 2, keeping the numbered list continuous:
🔧 Proposed fix
1. Copy this folder to a new repo root.
2. Run install and development:
-```bash
-pnpm install
-pnpm dev
-```
+ ```bash
+ pnpm install
+ pnpm dev
+ ```
3. Open <http://localhost:3000>.
4. Ask: "What time is it in Europe/Amsterdam?"📝 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.
| 1. Copy this folder to a new repo root. | |
| 2. Run install and development: | |
| ```bash | |
| pnpm install | |
| pnpm dev | |
| ``` | |
| 3. Open <http://localhost:3000>. | |
| 4. Ask: "What time is it in Europe/Amsterdam?" | |
| 1. Copy this folder to a new repo root. | |
| 2. Run install and development: |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 21-21: Ordered list item prefix
Expected: 1; Actual: 3; Style: 1/2/3
(MD029, ol-prefix)
[warning] 22-22: Ordered list item prefix
Expected: 2; Actual: 4; Style: 1/2/3
(MD029, ol-prefix)
🤖 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/agentic-monorepo-starter/README.md` around lines 13 - 22, In
docs/agentic-monorepo-starter/README.md the fenced code block after list item
"2." breaks the numbered list (MD029); fix by indenting the entire code fence
and its contents three spaces so the opening ```bash, its lines ("pnpm install",
"pnpm dev") and the closing ``` are aligned under list item 2, keeping items "3.
Open <http://localhost:3000>." and "4. Ask: ..." as the continued numbered list.
| const initialize = async () => { | ||
| if (initialized) { | ||
| console.warn('OneSignal should be initialized only once!'); | ||
| if (initialized || hasInitAttemptRef.current) { | ||
| return; | ||
| } | ||
| if (!person?.slug || !appId) { | ||
| return; | ||
| } | ||
| hasInitAttemptRef.current = true; |
There was a problem hiding this comment.
Listeners are permanently removed after init: initialized in the dep array + early-exit guard creates a listener lifecycle bug.
The sequence after a successful init:
OneSignal.init()succeeds →setInitialized(true)is called (line 203).- All event listeners are registered (lines 206–250).
- React re-renders because
initializedis nowtrue(it's in the dep array at line 304). - Effect cleanup runs → all listeners are removed (lines 257–303).
- Effect re-runs →
initialize()hitsif (initialized || ...)at line 160 and returns early. - Listeners are never re-registered. Notification clicks, permission changes, subscription changes, etc., all stop working silently.
The root cause is that registering listeners inside initialize() (which guards on initialized) and including initialized in the dep array creates a self-defeating cycle: the state change that marks success triggers the cleanup that erases the work.
Fix — separate listener registration from initialization:
🐛 Proposed fix
- const initialize = async () => {
- if (initialized || hasInitAttemptRef.current) {
- return;
- }
- if (!person?.slug || !appId) {
- return;
- }
- hasInitAttemptRef.current = true;
- try {
- // ... OneSignal.init() ...
- setInitialized(true);
- const isSubscribed = await hasPermission();
- setSubscribed(isSubscribed);
- OneSignal.Notifications.addEventListener('click', notificationClickHandler);
- // ... all other addEventListener calls ...
- } catch (err) {
- hasInitAttemptRef.current = false;
- console.log('Initialize error:', err);
- }
- };
- initialize();
- return () => {
- OneSignal.Notifications.removeEventListener('click', notificationClickHandler);
- // ... all other removeEventListener calls ...
- };
+ // Phase 1: only initialize once; listeners are wired in a separate effect below.
+ if (!initialized && !hasInitAttemptRef.current && person?.slug && appId) {
+ hasInitAttemptRef.current = true;
+ (async () => {
+ try {
+ const scope = serviceWorkerPath
+ ? `/${serviceWorkerPath.split('/').filter(Boolean)[0]}/`
+ : '/';
+ await OneSignal.init({ appId, safari_web_id: safariWebId, serviceWorkerPath, serviceWorkerParam: { scope }, /* … */ });
+ console.log('OneSignal initialized');
+ setInitialized(true);
+ const isSubscribed = await hasPermission();
+ setSubscribed(isSubscribed);
+ } catch (err) {
+ hasInitAttemptRef.current = false;
+ console.log('Initialize error:', err);
+ }
+ })();
+ }Then wire listeners in a separate effect that depends only on initialized:
+ React.useEffect(() => {
+ if (!initialized) return;
+ OneSignal.Notifications.addEventListener('click', notificationClickHandler);
+ // ... all other addEventListener calls ...
+ return () => {
+ OneSignal.Notifications.removeEventListener('click', notificationClickHandler);
+ // ... all other removeEventListener calls ...
+ };
+ }, [initialized]); // handlers must be stable refs (useCallback) or defined outsideAnd remove initialized from the initialization effect's dep array:
- }, [appId, safariWebId, serviceWorkerPath, router, initialized, person?.slug]);
+ }, [appId, safariWebId, serviceWorkerPath, person?.slug]);Also applies to: 251-253, 304-304
🤖 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 `@packages/notifications/src/components/notifications-subscriber.tsx` around
lines 159 - 166, The initialization effect's early-exit guard (initialize()
checking initialized and hasInitAttemptRef.current) plus having initialized in
its dependency array causes listeners registered during initialize() to be
removed on re-render and never re-registered; fix by moving all OneSignal event
listener registration/removal out of initialize() into a separate effect that
watches only initialized (e.g., create a new useEffect that subscribes to
OneSignal.on('notificationDisplay', ...), OneSignal.on('notificationClick',
...), onPermissionChange, onSubscriptionChange, etc., and returns a cleanup to
remove them), remove initialized from the dependency array of the existing
initialization effect that calls initialize() and keep the
hasInitAttemptRef/slug/appId guards there so initialize() only handles
OneSignal.init() and setInitialized(true).
Summary
docs/agentic-monorepo-startertemplate for teams to bootstrap a Vercel + GitHub AI-agentic monorepoTest plan
docs/agentic-monorepo-starter/**files are included in this PRmain(isolated from unrelated work)pnpm install && pnpm devMade with Cursor
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Documentation