Reference
What PR Lens does with your code: models, storage, retention
Where your code goes when PR Lens draws a pull request: the sandbox, the model, what lands in R2 and Redis, what gets deleted, and which retention claims the code does not support.
The GitHub App receives a webhook, verifies its signature, and starts a durable workflow. That workflow clones your repository at the pull request's head commit into a single use Upstash Box sandbox, where an agent reads the diff and the surrounding files and writes a JSON graph document. The document is validated, rendered to SVG, and written to Cloudflare R2. Redis holds pointers, run state and rate limits. The sandbox is deleted when the run ends. Then one comment on your pull request is updated in place.
This post says what the code does at each of those steps. Where the code does not answer a question, I say so rather than filling the gap, which is why the retention section below is shorter than you might want it to be. The hosted App is the surface with the most moving parts; the CLI and the agent skill have almost none, and I have put those at the end. For what the product is, what is PR Lens is the shorter page.
Which model reads your code, and where
Four ways to run PR Lens, and they differ mostly in this answer.
The hosted GitHub App runs a Gemini model inside the sandbox through a custom harness, and you supply no key of your own. Which model is set by configuration rather than baked in, and there is a second harness behind an environment flag that has to be turned on deliberately.
The GitHub Action runs in your own CI on a key you put in repository secrets. It asks Gemini by default and also speaks OpenAI or any compatible endpoint, so the request goes to whichever provider your key belongs to and no code reaches us at all.
The agent skill reasons with whatever model your coding agent already runs. There is no key to add, because the model is already in the room. It works in Claude Code, Cursor, OpenCode and any Agent Skills compatible tool.
The CLI draws locally. Only pr-lens analyze asks a provider, on a key of yours; validating, rendering and composing a comment need no network at all.
The sandbox
The hosted App's analysis runs in an Upstash Box sandbox created for that run and deleted afterwards. Several things about how it is set up are worth naming, because the pull request author controls the tree that lands inside it.
The clone is shallow, depth one, then the head commit is fetched by sha, because a pull request's head need not be the tip the shallow clone stopped at. History is weight the run never uses.
The GitHub installation token never enters the sandbox as a value. Git authenticates through the box's proxy, which injects the token as a request header for github.com, so nothing token bearing is written to .git/config, to an environment variable, or to any file the agent can read. The same pattern covers the model key: the box holds a placeholder value so the CLI selects API key authentication, and the proxy overwrites the outbound key header for the Gemini host with the real one. The sandbox holds a placeholder key, and the real credential lives in a proxy the sandbox cannot read.
Every GEMINI.md under the checkout is deleted before the agent starts, symlinks included. The CLI discovers a file with that name beside anything it reads and appends it to the read's result as project context, which is repository-authored instructions riding in on a tool result. A symlink is deleted rather than followed, because one pointing at the process environment would read that environment into the next request.
Everything the app writes for the agent, the diff, the map and the file inventory, lands in the run directory rather than in the checkout. The checkout is the author's tree, and writing into it would put our material somewhere the author can predict, name a file beside, or shadow with one of their own.
Runs are bounded twice, by a wall clock budget and by a turn cap, and the turn cap is deliberately set above what the clock affords so it only catches a loop that burns turns without burning time. The run is fire and forget: the agent posts its result to a webhook and the serverless function is never held open for the length of a model run.
What gets stored, and where
Two stores, and they hold different kinds of thing.
Cloudflare R2 holds the documents and the pictures. A pull request's graph document goes to graphs/<owner>/<repo>/pr/<number>/<headSha>.json. A repository baseline goes to graphs/<owner>/<repo>/baseline/<sha>.json. Every rendered SVG goes to renders/<owner>/<repo>/<contentHash>.svg, and the social card PNG sits beside it with the same shape. All of them are written with an immutable cache header. Where a key is addressed by what it contains, a baseline at its commit or a social card at its hash, the write lands only if the key is still free, so one key can never come to mean two things.
Redis holds pointers and coordination rather than source code: which baseline is current for a repository, the state of a workflow run, which sandboxes a run owns, which graph key belongs to a pull request, the id of the sticky comment, the short lived claim that makes comment creation exclusive, the sliding window rate limit counters per repository and per installation, and whether a repository is private.
Some of those carry an expiry set in code. The comment pointer expires after 60 days. The record of a run's sandboxes expires after 24 hours. The comment creation claim expires after 60 seconds and is heartbeated while the work runs.
Why the render URLs are hashes
Rendered SVGs are stored at keys derived from a SHA-256 of the SVG bytes, and served from an image route with a one year immutable cache header.
That is not tidiness. GitHub serves images in comments through its own proxy, which caches a URL effectively forever, so a stable filename would pin the first version of your diagram in place for the life of the pull request. Hashing the bytes means a redraw is a new URL, and a redraw that changed nothing is the URL you already had. The rest of what survives inside a GitHub comment is in animated SVG in GitHub comments.
The honest consequence: those image URLs are unguessable, and they are also unauthenticated. Anyone holding a link can fetch that image. The route serves nothing outside the renders prefix, so a URL reveals nothing that was not already linked from the comment, and the comment is only visible to people who can see the pull request. For a private repository the canvas link in the comment carries a signed token, and the images it embeds are content addressed the same way. If your threat model does not accept "the URL is the capability", the local paths at the end of this post are the ones to take.
What is deleted, and what is not
The sandbox is the part with a real deletion story. A box is recorded in Redis from the moment it is created, so it can be reclaimed even if the run that made it dies. Deletion is attempted at the end of the run; a confirmed deletion clears the record, and an unconfirmed one keeps it, because a box left running is a box still billing. Unconfirmed deletions are retried across workflow sleeps, in three rounds with a backoff, and the record outlives the run so nothing is left with no handle on it. If a box has not been created yet when something fails, the failure path insists on deletion with its own retries, since there is no record to reclaim it from.
Now the part people actually want a number for. The application sets no expiry on graph documents or rendered SVGs in R2, and there is no code path that deletes them when the App is removed from a repository. So I am not going to quote you a retention period for stored diagrams. The code does not set one, and a number I made up would be worse than this paragraph.
What the App writes to your repository
One comment per pull request, updated in place rather than replaced, plus a check run. It does not push commits, open pull requests, change settings or touch branches. The comment mechanism, including how it avoids adopting somebody else's comment, is in how PR Lens keeps exactly one comment per pull request, and the install itself is covered in installing the PR Lens GitHub App.
Webhook signature verification is mandatory in the code regardless of how the App is configured: an HMAC over the raw body, compared in constant time. A delivery that fails it is refused before anything else looks at it.
What the document cannot contain
Worth saying on a trust page. A PR Lens graph document has no findings field and no security lens, and a document that invents one is rejected rather than quietly stripped. Nothing in the pipeline is scanning your code for vulnerabilities or writing conclusions about it into a database. The output is a picture of structure.
Model derived text that does reach the comment is emitted as a code span rather than escaped prose, because GitHub autolinks URLs, emails and mentions after Markdown escaping runs. When the App fails to read your .github/pr-lens.yml, it logs the error's name and not its contents, because YAML parse errors include source excerpts and a source excerpt from a repository file is exactly where a secret would show up.
If you would rather nothing left your machine
Use the CLI or the agent skill. Both draw from a local diff, both write their output to .pr-lens/ in your working tree, and neither talks to any PR Lens service. pr-lens analyze is the single command that calls a provider, on your key, and you can skip it entirely by having your coding agent write the document instead. The full local workflow is in the PR Lens CLI.
Questions people ask
Is PR Lens safe to use on private repositories?
- The hosted App runs on private repositories, and the analysis happens in a single-use sandbox that is deleted when the run ends. Rendered diagrams are stored in object storage at keys derived from a hash of the image, and the URL is what grants access, so anyone holding a link can fetch that image. Whether that is acceptable is a call each team makes; if it is not, the CLI and the agent skill draw locally and store nothing.
Does PR Lens send my source code to a model?
- Yes, in the same sense that any analysis tool does. The hosted App clones your repository at the pull request head into a sandbox and an agent reads the diff and the surrounding files there, using a Gemini model. The GitHub Action does the same work with a key you supply, so the request goes to whichever provider that key belongs to. The agent skill uses whatever model your coding agent already runs, and no key is added.
How long does PR Lens keep my data?
- The analysis sandbox is deleted at the end of every run, and its deletion is retried until confirmed. Redis keys carry expiries: the comment pointer 60 days, the record of a run's sandboxes 24 hours. The graph documents and rendered SVGs in object storage have no expiry set by the application, so I will not quote you a retention period for those, because the code does not set one.
Can PR Lens write to my repository?
- It posts and edits one pull request comment and it writes a check run. It does not push commits, open pull requests, change settings or touch branches. The clone inside the sandbox is shallow and read only in practice: nothing from it is pushed anywhere.
Sources
Keep reading
Turn it on
Install the App and every pull request in the repositories you pick gets drawn. Or hand the prompt to the agent you already have open.