URL: /docs # PatchFlow Turn third-party SDK changes into evidence-backed, test-gated draft pull requests. PatchFlow watches npm packages, gathers the best migration evidence available, asks a cloud coding agent to update a repository, and proves the exact resulting commit with your required GitHub Checks. Humans still decide what merges. PatchFlow stops at a draft pull request labeled patchflow:ready or patchflow:needs-attention. ``` detect update → collect evidence → migrate call sites → open draft PR → run required checks → human review ``` ## Why PatchFlow Dependency bots are good at finding version drift. Breaking upgrades still leave engineers reading release notes, locating call sites, rewriting integrations, and waiting for CI. PatchFlow owns that middle mile. - Structured playbooks when PatchFlow knows the migration - Upstream release notes when an exact release is available - Explicit inference when structured evidence is unavailable - Fail-closed setup and tests before a PR is marked ready - Budgets, cooldowns, allowlists, and bounded retries ## Start here | If you want to… | Read | | --- | --- | | See the complete loop in five minutes | Quickstart | | Understand what the agent is allowed to do | Migration lifecycle | | Configure CI as the source of truth | Test gates | | Run unattended watch mode | Production operation | ## What it is not PatchFlow is not an auto-merge system, a general-purpose coding agent, or a guarantee that release notes are complete. It narrows an upgrade task, records the evidence used, and presents a tested change for review. > A green gate proves the configured checks passed. It does not prove that untested behavior is correct. --- URL: /docs/getting-started/install # Install Run PatchFlow from source, create a configuration, and verify the CLI. ## Requirements - Node.js 22.13 or newer - A GitHub repository with tests or GitHub Checks - A Cursor API key for cloud migration agents - GitHub access for PatchFlow: the PatchFlow GitHub App, or a token for local use - GitHub access for Cursor: its own integration, connected to the same repository ## Two GitHub connections, not one PatchFlow and Cursor reach your repository separately, and granting one does not grant the other. PatchFlow reads pull requests and checks and writes labels and comments. The migration itself runs on Cursor's infrastructure, so Cursor's own GitHub integration is what clones the repository, commits, pushes the branch, and opens the pull request. If Cursor is not connected to the repository, a run fails at agent start with "The SCM integration does not have access to repository". Connect it in the Cursor dashboard under Integrations. > PatchFlow's repository scoping governs what PatchFlow does. It does not constrain Cursor's integration, which is granted separately and may be broader. Review what you grant Cursor as its own decision, and note that repository contents are processed by Cursor when an agent runs. ## Install dependencies ``` npm install cp .env.example .env npm run patchflow -- --help ``` ## Create the first configuration The command writes patchflow.config.json without overwriting an existing file. Use --force only when you intend to replace it. ``` npm run patchflow -- init \ --repo https://github.com/acme/checkout \ --package stripe ``` ## Credentials ``` CURSOR_API_KEY=cursor_... GITHUB_TOKEN=github_pat_... ``` > PatchFlow never embeds the GitHub token in a clone URL. Private clones use a temporary askpass helper that is removed with the test workspace. --- URL: /docs/getting-started/quickstart # Quickstart Run a controlled migration against a real repository and inspect the resulting evidence. Start with a repository you own, a dependency upgrade already understood by your team, and a required CI check that exercises the affected integration. PatchFlow opens a draft pull request and never merges it. ## Choose the first repository - Choose a non-critical repository with a reproducible lockfile and test command. - Install the PatchFlow GitHub App on only that repository (hosted pilot) or use a short-lived GitHub token for local CLI evaluation. - For CLI runs, connect the same repository to Cursor separately. The hosted pilot launches agents with PatchFlow-managed provider credentials. - Name at least one required GitHub Check that covers the dependency integration. ## Configure a controlled upgrade ``` { "repos": [{ "url": "https://github.com/acme/checkout", "startingRef": "main", "packages": ["stripe"], "setupCommand": "auto", "testCommand": "npm test", "testGate": "checks", "requiredChecks": ["test"], "testEnvPassthrough": [] }], "policies": { "allowedUpdateTypes": ["patch", "minor", "major"] } } ``` ## Run a specific version transition ``` npm run patchflow -- run \ --package stripe \ --from 16.0.0 \ --to 17.0.0 \ --repo https://github.com/acme/checkout ``` ## What you should see - Evidence level playbook and update type major in the run log - A draft pull request upgrading the package and its call sites - GitHub Checks polled on an immutable PR head SHA - A stale verdict discarded automatically if the branch moves during verification - patchflow:ready when the gate passes - patchflow:needs-attention plus test output when it fails ## Run without a config file ``` npm run patchflow -- run -p stripe --from 16.0.0 --to 17.0.0 \ --repo https://github.com/acme/checkout \ --setup-command "npm ci" \ --test-command "npm test" ``` > A direct run may omit patchflow.config.json when --repo is supplied. The generated in-memory defaults still open a draft PR and allow one bounded retry. --- URL: /docs/concepts/lifecycle # Migration lifecycle The states, trust boundaries, and human decision points in one PatchFlow run. ``` migrating → pr_opened → testing → ready ↘ retrying → testing ↘ needs_attention ↘ error ``` ## 1. Detect and constrain PatchFlow resolves the target version, classifies the semantic update type, applies package and update-type policies, and creates a run record. ## 2. Migrate The agent receives one package, one version transition, the available evidence, the repository base branch, and a strict instruction to avoid unrelated refactors. ## 3. Draft PR The agent opens a pull request. PatchFlow converts it to draft when draftPullRequests is enabled, then reads the exact head SHA to gate. ## 4. Prove and label Required GitHub Checks must appear and succeed. Local mode clones the branch, completes repository setup, and then runs the configured test command. Setup failure is a gate failure. ## 5. Human review PatchFlow never merges. A ready label means the configured evidence and checks are ready for a human decision. --- URL: /docs/concepts/evidence # Migration evidence Know what PatchFlow knew before the agent changed your repository. | Level | Source | Interpretation | | --- | --- | --- | | playbook | Structured Before → After patterns | Strongest input | | release_notes | Exact upstream GitHub release | Source-backed | | inferred | Repository call sites and generic version context | Review closely | ## Evidence is not confidence theatre PatchFlow records the actual input class instead of presenting every migration as equally informed. The value is operational: teams can require more review for inferred migrations or restrict major updates until a playbook exists. ## What enters the prompt - Package and from/to versions - Semantic update type - Evidence level and source when available - Release-note excerpt - Structured Before → After patterns - Repository test command and strict edit constraints ## Current limit > Generic npm support does not synthesize structured API diffs. If matching release notes cannot be found, the run is explicitly marked inferred. --- URL: /docs/concepts/test-gates # Test gates Use required GitHub Checks as the production trust boundary, with local execution only for evaluation. | Mode | Behavior | Best for | | --- | --- | --- | | checks | Wait only for GitHub Checks | Production repositories | | local | Clone, set up, and test on the machine running the CLI | Local CLI evaluation only | | auto | Prefer checks; use local only if none appear | Early evaluation | ## Required checks When requiredChecks is non-empty, every named check must appear and finish with success. Missing, skipped, neutral, cancelled, or failed required checks do not pass. Leaving requiredChecks empty is weaker: any completed check can satisfy the gate, so a fast lint or preview-deploy job could pass a PR whose test workflow had not registered yet. PatchFlow warns when it runs unpinned and waits for the observed check set to stay unchanged for checkStabilizationSeconds (default 60) before trusting it. Name your checks in production. ``` { "testGate": "checks", "requiredChecks": ["lint", "unit", "integration"] } ``` ## Diff policy Before any test gate runs, PatchFlow checks that the agent's diff is actually a dependency upgrade: within the file-count limit, clear of forbidden paths, and leaving a manifest range that admits the target version. CI definitions are forbidden by default, because an agent that can edit its own test gate can pass it. A diff-policy failure short-circuits the test gate. The check runs produced by a modified workflow are exactly the evidence PatchFlow must not trust, so it labels the PR patchflow:needs-attention without reading them. ``` { "policies": { "diffPolicy": { "maxChangedFiles": 50, "forbiddenPaths": [".github/workflows/", ".github/actions/"], "requireManifestBump": true } } } ``` ## Local setup Auto setup selects npm ci, pnpm, Yarn, Bun, or npm install from the repository lockfile. A setup failure ends the gate before tests run. Set setupCommand to none only for a self-contained test command. ``` { "setupCommand": "auto", "testCommand": "npm test", "testGate": "local" } ``` ## Commit-bound verdicts PatchFlow evaluates diff policy and checks against one immutable PR head SHA. If the branch moves while verification is running or while the ready label is being applied, the stale result is discarded and the label is removed. Branch protection and required human approval remain the merge authority. The PR comment records the exact verified commit for auditability. ## Retries A failed gate may resume the same agent with a bounded test-output excerpt. maxRetries is capped at three. Every attempt re-reads the PR head SHA before gating again; infrastructure failures never instruct the agent to modify code. --- URL: /docs/guides/production # Production operation Bound unattended work with policies, budgets, cooldowns, and required checks. ## Start conservative ``` { "policies": { "allowedUpdateTypes": ["patch", "minor"], "packageAllowlist": ["stripe", "@aws-sdk/client-s3"], "packageDenylist": [], "maxConcurrentMigrations": 1, "maxRunsPerCycle": 5, "cooldownMinutes": 60, "draftPullRequests": true, "maxRetries": 1, "maxAttemptsPerVersion": 2 } } ``` ## Run one poll cycle Use one-cycle mode in a scheduler first. It is easier to observe and gives the scheduler ownership of restarts. ``` npm run patchflow -- watch --once ``` ## Then run continuously - Set requiredChecks for every production repository. - Keep draftPullRequests enabled. - Use maxRunsPerCycle as the hard agent-run budget per cycle, and maxAttemptsPerVersion as the budget per upgrade. - Increase concurrency only after CI and rate limits are understood. - Review inferred and major migrations more carefully. ``` npm run patchflow -- watch ``` ## Budgets stop runaway spend A migration that can never pass would otherwise be rediscovered as new work on every poll, starting a fresh cloud agent each time. maxAttemptsPerVersion (default 2) abandons a (package, version, repository) triple once it is exhausted, and records the version as seen so the next cycle moves on. cooldownMinutes (default 60) spaces out consecutive attempts against the same repository. > State writes are serialized by an in-process mutex and an advisory lock file, so a second watch process fails fast rather than silently overwriting the first. This is advisory, not a distributed lease: it does not coordinate across machines or shared filesystems. --- URL: /docs/guides/private-repos # Private repositories Give PatchFlow the narrowest repository access that supports PRs, checks, and isolated clones. ## GitHub access PatchFlow does not need repository contents write access. With the managed agent, the agent pushes the branch through its own GitHub integration; with the customer-hosted runner, your own workflow pushes it with your repository's token. The PatchFlow GitHub App uses Checks read, Contents read, Issues write, Pull requests write, and Actions write on repositories you select. Local CLI evaluation may use a short-lived personal access token for Check Runs — keep it scoped and never treat it as the hosted SaaS boundary. - Repository contents: read for PatchFlow diff inspection and optional clones - Pull requests: read and write - Issues: write for labels and comments - Checks: read - Metadata: read - Actions: read and write, used only to start, read, and cancel the customer-hosted runner workflow ## Clone authentication For local gates, PatchFlow gives Git a temporary askpass program and token file inside the disposable gate directory. The clone URL and command arguments remain secret-free, and the entire directory is removed after the gate. ## CI secrets > PatchFlow does not copy CI secrets into the agent prompt. GitHub-hosted checks execute under the repository’s existing workflow permissions. --- URL: /docs/guides/customer-hosted-runner # Customer-hosted runner Run the migration in your own GitHub Actions with your own Claude or Codex key. PatchFlow keeps the evidence, the gate, and the draft PR. With the customer-hosted runner, the coding agent runs in your repository's GitHub Actions, under your model account, with your repository's own permissions. PatchFlow never receives your model key, and no third-party coding-agent integration needs access to your repository. PatchFlow still owns what makes the result trustworthy: the release evidence and prompt, a server-side check of the pushed branch, the draft pull request, and the readiness verdict from your required Checks on the exact head commit. ## Install - Save https://patchflow.run/runner/patchflow-migrate.yml as .github/workflows/patchflow-migrate.yml on your default branch. Read it first; it is short. - Add one repository secret: ANTHROPIC_API_KEY for Claude Code (default), or OPENAI_API_KEY for Codex and set the repository variable PATCHFLOW_AGENT to codex. - Optional: set PATCHFLOW_MODEL to pin a model. - Private npm packages: add the secret NPM_TOKEN and the variable NPM_REGISTRY_URL. Only the agent job can read them. - In the pilot workspace, choose Your own GitHub Actions runner in step 2. - Make sure every required Check runs on pull_request events. The runner pushes with your repository's GITHUB_TOKEN, and GitHub does not start push-triggered workflows for pushes made with that token, so only the pull request PatchFlow opens will run your CI. ``` curl -fsSL https://patchflow.run/runner/patchflow-migrate.yml \ -o .github/workflows/patchflow-migrate.yml ``` ## How a run works - PatchFlow dispatches the workflow with the run id, the inspected commit, the branch name, and the migration prompt. - The agent job checks out the inspected commit with a read-only token, runs Claude Code or Codex with your key, and uploads a patch. - The publish job never sees your model key and never runs agent code. It rejects changes under .github/ or above the file cap, then pushes only patchflow/. - PatchFlow re-checks the branch on its side: it must descend from the inspected commit, stay inside the diff policy, and leave a root or workspace package.json that admits the target version. Only then does it open a draft pull request. - Your required Checks run on that pull request (they must be triggered by pull_request). It is ready only when they pass on its exact head commit. Nothing merges automatically. ## What PatchFlow can and cannot see - Sees: workflow run status, the pushed branch, its diff, and your Check results. - Never sees: your model key, your npm token, or your model provider's bill. Cost in the Migration Proof Report shows as paid by you. - The workflow is yours: you can change it, and PatchFlow treats whatever it pushes as untrusted until its own checks and your Checks pass. ## Limits > Early access during the private pilot. Every action in the template is pinned to a commit SHA; review updates to the template like any other dependency change. --- URL: /docs/guides/recover-failed # Recover a failed migration Read the evidence, inspect the gate, and deliberately retry or take over. ## Inspect recent runs Status includes the package transition, update type, evidence level, PR URL, and last update time. ``` npm run patchflow -- status ``` ## Read the PR comment - Which gate ran - Which required checks were missing or failed - Whether repository setup failed - The bounded output excerpt sent to the retry agent ## Retry manually A full ID or unambiguous prefix is accepted. Manual retry creates a new run so the audit history remains intact. ``` npm run patchflow -- retry 8f3c2a1b ``` ## Override a policy ``` npm run patchflow -- retry 8f3c2a1b --force ``` > Force bypasses package and update-type policy only. It does not bypass test gates, required checks, draft PRs, or the human merge decision. --- URL: /docs/reference/cli # CLI reference Commands, significant flags, and exit behavior. | Command | Purpose | | --- | --- | | patchflow init | Write a starter configuration | | patchflow run | Run one package migration | | patchflow watch | Poll and migrate configured packages | | patchflow retry | Create a new attempt from a previous run | | patchflow status | List recent local runs | ## run ``` patchflow run -p [--from ] [--to ] [--repo ] [--starting-ref ] [--setup-command ] [--test-command ] [--test-gate auto|checks|local] [--auth app|token] [--config ] [--force] ``` ## watch ``` patchflow watch [--once] [--config ] ``` ## retry ``` patchflow retry [--config ] [--force] ``` ## Exit codes | Result | Code | | --- | --- | | ready | 0 | | needs_attention or error | 1 | --- URL: /docs/reference/config # Configuration reference Every repository, timing, model, gate, and operational-policy setting. ## Complete example ``` { "repos": [{ "url": "https://github.com/acme/checkout", "startingRef": "main", "packages": ["stripe"], "setupCommand": "auto", "testCommand": "npm test", "testGate": "checks", "requiredChecks": ["lint", "test"], "testEnvPassthrough": [] }], "pollIntervalMinutes": 60, "model": "composer-2.5", "checkTimeoutMinutes": 15, "checkPollIntervalSeconds": 20, "checkStabilizationSeconds": 60, "policies": { "allowedUpdateTypes": ["patch", "minor", "major"], "packageAllowlist": [], "packageDenylist": [], "maxConcurrentMigrations": 1, "maxRunsPerCycle": 10, "cooldownMinutes": 60, "draftPullRequests": true, "maxRetries": 1, "maxAttemptsPerVersion": 2, "diffPolicy": { "maxChangedFiles": 50, "forbiddenPaths": [".github/workflows/", ".github/actions/"], "requireManifestBump": true } } } ``` ## Repository fields | Field | Default | Notes | | --- | --- | --- | | startingRef | main | Agent base branch | | setupCommand | auto | auto, none, or a shell command | | testCommand | npm test | Runs only after setup succeeds | | testGate | auto | auto, checks, or local | | requiredChecks | [] | Exact GitHub Check names | | testEnvPassthrough | [] | Extra environment variable names the test command may see | ## Timing | Field | Range/default | | --- | --- | | pollIntervalMinutes | above 0; default 60 | | checkTimeoutMinutes | above 0; default 15 | | checkPollIntervalSeconds | above 0; default 20 | | checkStabilizationSeconds | 0 or greater; default 60 | ## Policy limits | Field | Range/default | | --- | --- | | maxConcurrentMigrations | 1–10; default 1 | | maxRunsPerCycle | 1–100; default 10 | | cooldownMinutes | 0 or greater; default 60 | | maxRetries | 0–3; default 1 | | maxAttemptsPerVersion | 1–20; default 2 | | draftPullRequests | default true | ## Diff policy | Field | Range/default | | --- | --- | | diffPolicy.maxChangedFiles | 1 or more; default 50 | | diffPolicy.forbiddenPaths | default [".github/workflows/", ".github/actions/"] | | diffPolicy.requireManifestBump | default true | --- URL: /docs/reference/run-states # Run states and labels Interpret local state, pull-request labels, and terminal outcomes. | State | Meaning | | --- | --- | | pending | Run created but not started | | migrating | Cloud agent is working | | pr_opened | PR located; gate not started | | testing | Commit-bound verification running | | retrying | Agent resumed with gate output | | ready | Gate passed; human review remains | | needs_attention | Retry budget ended without a passing gate | | error | Pipeline could not complete | ## PR labels PatchFlow removes the opposite label when an outcome changes. It also writes a PR comment with the gate mode, evidence, and exact verified commit SHA. - patchflow:ready — configured gate passed - patchflow:needs-attention — configured gate failed ## Local state State stores the most recent 100 runs and the last-seen package version. Writes are atomic and each pipeline update reloads current state before saving. ``` .patchflow/state.json ``` --- URL: /docs/operations/security # Security model Understand credentials, code execution, trust boundaries, and the merge boundary. ## Credentials - Hosted pilot: PatchFlow supplies the migration-provider credential. You never paste it into the workspace. - CLI evaluation: CURSOR_API_KEY starts and resumes cloud agents on the machine running PatchFlow. - GitHub App installation tokens read Checks and Contents and write Pull requests, labels, and comments only on repositories you select. - Private clone credentials are temporary and never enter clone URLs. Tokens are not included in migration prompts. ## Two independent GitHub grants PatchFlow's GitHub App is installed by you and scoped to the repositories you select. Every operation is checked against that selection, and a repository outside it is refused before an agent is started. The coding agent uses a separate GitHub integration to clone, commit, and open the pull request. Installing PatchFlow's App does not grant that agent anything, and revoking PatchFlow does not revoke the agent's access. > This is the boundary most worth understanding: PatchFlow's scoping constrains PatchFlow, not the agent provider. Grant agent GitHub access deliberately and review it separately. ## The local gate runs agent-authored code When you use the local CLI test gate, PatchFlow clones a branch an agent wrote and runs setupCommand and testCommand on that machine. Those commands receive a stripped environment: PATH, HOME, locale, temporary directories, and CI=1. Provider keys and cloud credentials are not visible to them. Use testEnvPassthrough to name any additional variable a repository genuinely needs, such as a private registry token. Each name you add is deliberately reachable by the code under test. > A stripped environment is not a sandbox. Prefer customer-owned GitHub Checks for production trust. Treat patchflow.config.json as code: setupCommand and testCommand are shell commands. ## Managed execution boundary For production use, customer-owned GitHub Checks are the supported execution boundary: your tests run in infrastructure and with secrets you control. PatchFlow does not currently offer a generally available hosted sandbox for running customer test code. Until that exists, required Checks on your repository remain the fail-closed gate. ## Registry metadata boundary PatchFlow discovers newer npm versions only for dependencies whose repository evidence routes them to the public npm registry. It uses package-lock resolved URLs and committed .npmrc registry mappings; unscoped packages without an override follow npm's public default. Scoped packages without registry evidence remain private or unknown and are not sent to the public registry. For an eligible dependency, PatchFlow sends only its package name to registry.npmjs.org and reads the latest stable dist-tag. It does not send the repository name, source code, manifest, lockfile, GitHub identity, or credentials. The workspace shows registry provenance with the proposed upgrade. PatchFlow does not currently accept private registry credentials. Private and ambiguous dependencies fail closed until a customer-owned registry access path is available; PatchFlow never falls back from a private registry to public npm. ## Human merge boundary PatchFlow opens draft PRs and never merges. Repository branch protection and required human approval remain the final authority. ## Output handling Gate output is truncated before storage, PR comments, and agent retry prompts. Avoid tests that print credentials; PatchFlow cannot reliably redact arbitrary application output. --- URL: /docs/operations/limits # Current limits What the MVP deliberately supports—and where it stops. ## Supported - npm package version discovery - Registry-aware public npm opportunity discovery for up to 40 root dependencies per inspection - Hosted npm watch for enrolled pilot repos (queues operator review; does not auto-launch) - GitHub repositories, pull requests, labels, comments, and Checks - Cursor cloud migration agents (managed) or the customer-hosted runner in your GitHub Actions (Claude Code or Codex, your key) - Private npm packages during a customer-hosted run, using your registry token inside your own workflow - Node-oriented automatic local setup plus custom setup commands - Concurrent migrations in one process, with state writes serialized by a mutex and an advisory lock file - Diff-scope policy on agent pull requests, including a default ban on editing CI definitions ## Not yet supported - Python, Go, Java, or other registry watchers - Private registry version discovery, or private registry credentials held by PatchFlow - GitLab or Bitbucket - Unattended multi-tenant auto-launch from registry watches - Automatic merge - Guaranteed structured API diffs for arbitrary npm packages - Self-service billing, dashboards, SSO, and organization-wide audit export - A generally available PatchFlow-hosted execution sandbox for customer test code ## Design rule > When PatchFlow lacks structured evidence, it says inferred. When setup fails, the gate fails. When a required check is missing, the gate waits and fails. Uncertainty should stay visible. --- URL: /docs/agents # Agent-readable documentation Give coding agents the same product contract developers read. Every page exposes Copy Markdown and Open Markdown. The complete documentation index is also available at /llms.txt. ## Use with a coding agent ``` Read https:///llms.txt first. Then follow the Quickstart and Security model pages. Do not bypass PatchFlow policies or test gates. ``` ## Important invariants - Never put GITHUB_TOKEN in a clone URL. - Never treat inferred evidence as a structured playbook. - Never continue to tests after repository setup fails. - Never mark a PR ready while a required check is missing. - Never merge automatically. --- # PatchFlow Blog URL: /blog/first-measured-typescript-migration # Our first measured migration: TypeScript 5.9 to 6.0 What a real private-pilot migration cost, where it failed, and why a red check was the most useful result. Published: 2026-08-01 Author: PatchFlow Engineering Tags: Field report, TypeScript, CI The first PatchFlow private-pilot migration did not travel in a straight line. That is precisely why it was useful. We selected a repository we own, pinned one transition—TypeScript `5.9.3` to `6.0.2`—and named one customer-owned GitHub Check, `smoke`, as the verification boundary. PatchFlow could inspect the repository, ask a bounded coding agent to make the upgrade, and open a draft pull request. It could not merge. ## The measured result The coding-agent attempt completed in roughly three minutes and reported **83.43 cents** of execution cost. The resulting draft pull request changed 16 files. It pinned TypeScript 6 across the workspace, added explicit Node and Vite types, and adapted the configuration for TypeScript 6 defaults. Local verification reported: | Gate | Result | | --- | --- | | Workspace typecheck | 9 packages passed | | Tests | 33 tests passed before the baseline repair | | Builds | Web, admin, and marketing passed | | Required GitHub Check | Failed | That last row mattered more than the first three. ## The failure was real—but not caused by the migration The `smoke` workflow failed all three offline agent fixtures with an empty error. We reproduced the same failure on the exact pre-migration commit. The TypeScript change had not caused it; the repository's smoke harness already depended on tenant/database state despite calling itself offline. It would have been easy to wave that away. A migration product cannot do that. The named check is the repository owner's boundary, not a suggestion. We fixed the harness separately. Offline runs now avoid tenant storage and use deterministic in-memory tools. Its own pull request passed the hosted smoke check, was reviewed, and merged before the TypeScript branch was refreshed. ## A false green we had to remove The pilot also exposed a PatchFlow defect: the run was initially recorded as having an open PR before the required check had settled. That state was too optimistic. We changed the control plane so a run becomes ready only after PatchFlow: 1. resolves the draft pull request; 2. records its exact head SHA; 3. waits for every named check on that SHA; 4. fails closed on missing, pending, cancelled, or failed checks; and 5. persists the settled verdict and provider cost. When the base branch later advanced with the smoke repair, the verifier rejected the stale relationship. We added a narrow recheck rule: GitHub must prove that the new base descends from the originally inspected commit **and** that the checked pull-request head contains the new base. The original policy was not replaced with “trust the latest.” It became an ancestry proof. ## What finally merged The refreshed TypeScript branch passed the hosted `smoke` check on its new exact head. PatchFlow reconciled the existing run without launching another paid agent. A human reviewed and merged the draft pull request. The outcome was one completed migration, one repaired repository check, one hardened control plane, and a real cost measurement. That is the standard we want for the pilot: not a perfect demo, but a bounded system that tells the truth when the path is messy. ## What this does not prove One repository and one migration do not establish broad compatibility. A passing smoke check proves only the configured check passed on the recorded commit. It does not prove untested behavior is correct. The next migrations should deliberately vary the package, repository shape, and check topology. The goal is not a bigger automation claim. It is a larger body of measured evidence. --- URL: /blog/dependency-bots-stop-at-the-hard-part # Dependency bots stop where the engineering starts Version detection is useful. Breaking upgrades still leave teams with evidence gathering, call-site changes, and CI interpretation. Published: 2026-07-29 Author: PatchFlow Engineering Tags: Dependency management, Engineering productivity, Product A dependency-update alert answers one question: **is a newer version available?** For patch releases, that may be most of the work. For a breaking SDK or framework release, it is only the first line of the ticket. The remaining work usually looks like this: - find the release notes that actually match the target version; - separate API breaks from unrelated announcements; - locate the affected call sites and configuration; - update the package manifest and lockfile together; - adapt code without drifting into a refactor; - run the repository's real setup and checks; and - explain the result well enough for a human to review it. That is the middle mile PatchFlow is designed to own. ## Detection and migration are different products Detection is mostly a graph problem: inspect manifests, resolve versions, and apply an update policy. Migration is repository-specific engineering under uncertainty. A useful migration system must carry more context than “upgrade package X.” It needs the exact before and after versions, the evidence class behind the change, the repository's required checks, and a strict edit boundary. It also needs to preserve uncertainty instead of converting every result into a green badge. PatchFlow records migration evidence as one of three levels: | Level | Meaning | | --- | --- | | Structured playbook | Known before-and-after patterns for the transition | | Release notes | Exact upstream release evidence is available | | Inferred | The agent must reason from the repository and generic version context | An inferred migration can still be useful. It should simply receive more human scrutiny than a transition backed by a precise playbook. ## “Tests passed” needs a subject CI status is not a free-floating fact. A verdict belongs to a commit. If a branch moves after tests start, the old result cannot authorize the new head. If a required check never appears, absence cannot mean success. If an agent can edit the workflow that judges its own patch, the check is not an independent boundary. PatchFlow therefore treats verification as a tuple: ```text repository + pull request + immutable head SHA + named checks ``` The system waits for those checks and records the exact commit they evaluated. Missing or failed checks stop the run. Humans retain merge authority. ## The product is the review boundary The interesting promise is not “AI updates dependencies.” Coding agents can already change code. The product is a controlled path from a postponed upgrade to a draft pull request with enough evidence, scope control, verification, and cost data for an engineer to make a decision. That framing is narrower than autonomous maintenance. It is also far more useful to a team deciding whether to trust the first migration. --- URL: /blog/commit-bound-ci-verdicts # A green CI badge is meaningless without a commit Why migration verdicts must be bound to immutable pull-request heads—and what to do when the base branch advances. Published: 2026-07-25 Author: PatchFlow Engineering Tags: CI, Security model, GitHub Imagine a migration branch passes CI. A minute later, another commit lands on that branch. The pull request still displays recent green activity, but the code a reviewer sees is no longer the code that produced the result. This is not a theoretical edge case. Any asynchronous automation that edits branches and polls CI must decide what its green verdict actually refers to. ## Bind the verdict to the head SHA PatchFlow reads the pull request's immutable head SHA before evaluating required GitHub Checks. The stored result includes that SHA. If the branch moves, the old result is stale and cannot make the run ready. The core rule is small: ```text required checks passed on head A ≠ required checks passed on head B ``` Named checks matter too. An unpinned system may see a quick lint job finish before a slower integration workflow even registers. Production policies should name the checks that exercise the affected integration. Missing named checks fail closed. ## Base branches move as well Longer-running pull requests introduce another relationship. The repository's base branch may advance after the migration was inspected. Blindly accepting the latest base weakens the original authorization boundary. Requiring the base to remain byte-for-byte identical forever makes a safe recheck impossible after normal repository work. The narrow solution is ancestry: 1. the current base must descend from the originally inspected commit; 2. the pull-request head must contain the current base; and 3. required checks must pass on that exact head. GitHub's commit comparison API can prove both relationships. A diverged base or stale head is rejected. ## Keep merge authority separate A passing verification record should never bypass branch protection or human review. PatchFlow opens draft pull requests and stops. This separation produces a cleaner security model: - PatchFlow can propose and verify. - Customer-owned GitHub Actions can test. - Branch protection and reviewers can authorize merge. No single component gets to write the change, define the test, and merge the result. ## What to record A useful audit record for an automated migration includes: - repository and pull-request number; - original inspected commit; - current base and verified head SHA; - required check names and conclusions; - provider attempt and model; - settled execution cost; and - the human-controlled merge outcome. That record turns “the bot said it passed” into a claim another engineer can inspect.