Skip to content
Prabal Pratap Singh
All work

AI dev tools · GitHub App2026Live

Landpatch

GitHub App that migrates code off the shut-down OpenAI Assistants API. It finds the call sites, has a model patch them onto the Responses API, verifies the patch in a sandbox and opens a pull request only when verification passes.

landpatch.up.railway.app
Landpatch website preview

At a glance

5 of 6

  • Call sites migrated in a real PR (PrabalParihar/chatbot#1, +83 −64); the 6th held for a maintainer
  • PR opens only after a 10-minute sandbox passes: install, typecheck, lint, tests, contract test
  • $2 LLM budget per run, enforced before a model call starts

What it is

Turn a breaking API shutdown into a reviewed pull request that has been verified in a sandbox, without giving a bot unchecked write access to the repository.

Why it exists

OpenAI shut down the Assistants API on 26 August 2026, and every app still calling threads, runs and assistants broke. The migration is mechanical but easy to get subtly wrong, and nobody wants an AI bot pushing unverified code into their repository.

How it works

Installing the GitHub App sends a webhook to a Hono API, which stores the repositories and queues a run on pg-boss in Postgres. A worker shallow-clones the repository, finds Assistants call sites with ast-grep rules and confirms the receiver with ts-morph, then asks a model (OpenAI, Anthropic or OpenRouter) for a patch scoped to one function. The patch is verified in a Docker sandbox, and the pull request is opened only if that passes; otherwise the draft is stored on a Next.js dashboard with its diff and cost.

Decisions worth explaining

Guardrails refuse, they don't just report

The $2 budget, the 10-minute sandbox and the three-job concurrency limit are checked before work starts. A model call that would cross the estimate never runs, instead of being flagged after the money is spent.

Hold hard cases for a person

Moving files attached to an assistant into a vector store needs a human decision, so that call site is held and the reason is written into the pull request. An assistant id with no local config becomes a TODO that throws until OPENAI_MODEL and OPENAI_INSTRUCTIONS are set.

Least-privilege app, signed webhooks

The app asks only for contents, pull requests, checks, metadata and issues, never administration, secrets or Actions. Every webhook delivery is signature-checked and stored once per delivery id, so a retried delivery never starts a second run.

Results

  • Live as a GitHub App, with three Railway services (web, API, worker) on one Dockerfile plus Postgres.
  • Opened PrabalParihar/chatbot#1: 5 call sites moved to the Responses API and 1 held for a maintainer with the reason written down.
  • pnpm and Turborepo monorepo: scanner, patch loop, sandbox, GitHub and migration-pack packages; self-hostable with Docker.

Stack

TypeScriptNext.jsHonoPostgreSQLpg-bossDrizzleast-grepts-morphDockerGitHub AppsRailway

From the code

packages/core/src/guardrails.ts

/**
* Hard limits for a migration run. Later stages must refuse work that would
* cross these, not merely report it after the fact.
*/
export const GUARDRAILS = {
llmBudgetUsdPerRun: 2,
sandboxLimitMs: 10 * 60 * 1000,
maxConcurrentWorkers: 3,
} as const;
export function assertWithinLlmBudget(spentUsd: number,
limitUsd: number = GUARDRAILS.llmBudgetUsdPerRun): void {
if (spentUsd > limitUsd) throw new LlmBudgetExceededError(spentUsd, limitUsd);
}

Want something like this built?

Open to roles & contracts. Remote · IST with US/EU overlap.

Email me