Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions app/[username]/[repo]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Link from "next/link"

import { isAppInstalledForRepo } from "@/lib/github/install-check"

interface LayoutProps {
children: React.ReactNode
params: {
username: string
repo: string
}
}

export default async function RepoLayout({ children, params }: LayoutProps) {
const { username, repo } = params

// Build GitHub App installation URL if available
const appSlug = process.env.NEXT_PUBLIC_GITHUB_APP_SLUG
const installUrl = appSlug
? `https://github.com/apps/${appSlug}/installations/new`
: undefined

const installed = await isAppInstalledForRepo({ owner: username, repo })

Comment on lines +13 to +23
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Force Node runtime and dynamic rendering for Octokit call

Without fetch-based signals, Next may attempt static optimization or edge runtime, breaking Octokit or caching the install state. Explicitly pin Node and disable caching.

Apply this diff near the top-level (after imports is fine):

+export const runtime = "nodejs"
+export const dynamic = "force-dynamic"
+export const revalidate = 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.

Suggested change
export default async function RepoLayout({ children, params }: LayoutProps) {
const { username, repo } = params
// Build GitHub App installation URL if available
const appSlug = process.env.NEXT_PUBLIC_GITHUB_APP_SLUG
const installUrl = appSlug
? `https://github.com/apps/${appSlug}/installations/new`
: undefined
const installed = await isAppInstalledForRepo({ owner: username, repo })
export const runtime = "nodejs"
export const dynamic = "force-dynamic"
export const revalidate = 0
export default async function RepoLayout({ children, params }: LayoutProps) {
const { username, repo } = params
// Build GitHub App installation URL if available
const appSlug = process.env.NEXT_PUBLIC_GITHUB_APP_SLUG
const installUrl = appSlug
? `https://github.com/apps/${appSlug}/installations/new`
: undefined
const installed = await isAppInstalledForRepo({ owner: username, repo })
🤖 Prompt for AI Agents
In app/[username]/[repo]/layout.tsx around lines 13 to 23, Next may statically
optimize or choose an edge runtime which breaks the Octokit/install check; add
top-level exports immediately after the imports to force Node runtime and
disable caching: export runtime = 'nodejs', export dynamic = 'force-dynamic',
and export fetchCache = 'no-store' so the layout runs on Node, always renders
dynamically, and avoids cached fetches for the isAppInstalledForRepo call.

if (!installed) {
return (
<main className="container mx-auto p-4">
<div className="mb-6 rounded-md border border-yellow-300 bg-yellow-50 p-4 text-yellow-900">
<h1 className="text-xl font-semibold mb-2">
Issue to PR GitHub App is not installed for {username}/{repo}
</h1>
<p className="mb-4">
To use Issue to PR features on this repository, install the GitHub App.
</p>
{installUrl ? (
<Link
href={installUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center rounded-md bg-stone-900 px-4 py-2 text-white hover:bg-stone-800"
>
Install Issue to PR on GitHub
</Link>
) : (
<div className="text-red-700">
The GitHub App slug is not configured. Please set NEXT_PUBLIC_GITHUB_APP_SLUG.
</div>
)}
</div>
</main>
)
}

return <>{children}</>
}

32 changes: 32 additions & 0 deletions lib/github/install-check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { getInstallationFromRepo } from "./repos"

/**
* Checks whether the Issue-to-PR GitHub App is installed for the given repository.
* Returns true when installed, false when not installed or when the check fails
* with a 404 from GitHub. Other errors are logged and treated as not installed
* to avoid breaking the UX.
*/
export async function isAppInstalledForRepo({
owner,
repo,
}: {
owner: string
repo: string
}): Promise<boolean> {
try {
await getInstallationFromRepo({ owner, repo })
return true
} catch (error: unknown) {
// Octokit errors expose a numeric `status` code
const status = (error as { status?: number })?.status
if (status === 404) {
return false
}
console.error(
`[github/install-check] Failed to determine installation for ${owner}/${repo}:`,
error
)
return false
Comment on lines +25 to +29
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Sanitize error logging to avoid leaking sensitive details

Octokit errors can include request metadata. Log only safe fields (status, name, message) instead of the whole error object.

Apply this diff:

-    console.error(
-      `[github/install-check] Failed to determine installation for ${owner}/${repo}:`,
-      error
-    )
+    const err = error as any
+    const message = err?.message ?? "unknown"
+    const name = err?.name ?? "Error"
+    console.error(
+      `[github/install-check] install check failed for ${owner}/${repo}: status=${status} name=${name} message=${message}`
+    )

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In lib/github/install-check.ts around lines 25 to 29, the current console.error
prints the entire Octokit error object which may include sensitive request
metadata; modify the error logging to extract and log only safe fields (status,
name, message) from the error (use optional chaining/defaults) and include a
concise contextual message like "[github/install-check] Failed to determine
installation for owner/repo:" followed by a JSON or stringified object
containing only { status, name, message } to avoid leaking sensitive details.

}
}