Privacy¶
v0: no network unless you ask, no telemetry¶
- No network call unless you ask for one: by configuring the model adapter (below), by running a
command that reads a package index (
pack synth,outdatedorupgrade, below), or withupgrade --apply, which installs packages with uv, andupgrade --agent, which starts your ownclaudeprogram. There is no update check, no pack download and no crash report.scan,verify,undoandmcpnever reach a network, andfixreaches only a model endpoint you configured. - No telemetry code at all: no usage counters, no identifiers, no ping.
- No account.
- Everything Obelize writes goes into the repository you pointed it at, bar three places. The
cache in
$XDG_CACHE_HOME/obelize(or~/.cache/obelize) holds what the package index answered, the releases it sent, the packspack synthandupgrademade and, while a trial runs, its copy of the repository and its environments (below);pack newandpack synthwrite the pack's folder where--dirsays. While verification commands run, their JUnit XML, which can hold test output, sits in a temporary directory under your system's temp folder and is removed afterwards. When you approve a verification command from a repository's.obelize.yml, the repository's path, the command and its sha256 are kept in your own~/.config/obelize/approved.json.
Anything a future version adds that talks to a network will be opt-in and documented here, with exactly what it sends.
obelize mcp opens no socket. It answers a coding agent on standard input and output with what the
commands print and, for obelize_residue, the code around each place left, redacted as a model's
context is. Where that text goes next is the agent's, which you chose.
What is written to .obelize/¶
Every run writes .obelize/runs/<run-id>/ in the migrated repository, on your disk; nothing
uploads it. The layout and every field are in RUN_FOLDER.md. What the
files may contain:
| File | Contains |
|---|---|
run.json |
No file content and no command output: the verification's output is a path into verify/ |
findings.json |
Paths, positions, symbols and verdicts, and no source: Finding.evidence is never written |
plan.json |
Paths, line numbers and hashes, and no source |
patch.diff, snapshots/ |
Your source, not redacted: changed lines with three lines of context, and each written file whole, before and after |
verify/<phase>/*.log |
What your commands printed, redacted and capped |
REPORT.md |
A readable summary of the other files |
model/ |
Only with a model configured (below) |
What reduces the exposure:
- Command output and model context are redacted before they are written or sent, by one redactor whose rules and limits are in THREAT_MODEL.md. It misses secret formats it has no pattern for.
- Environment variables are never recorded.
- Command output is capped at 1 MiB per command (the first 256 KiB plus the last 768 KiB).
- Git ignores
.obelize/: obelize writes a.gitignoreinside it and never edits yours.
patch.diff and snapshots/ hold your code exactly as it is, secrets included, because a
snapshot you cannot restore from is useless. Review a run folder before you share it.
What the package index sees¶
pack synth and upgrade read two releases of a library, and outdated asks about every library
your manifests declare, so they ask a package index:
- The index is yours: PyPI unless your own
~/.config/obelize/config.yml(network.index) orOBELIZE_INDEX_URLnames another. A repository's.obelize.ymlcannot name one and is refused. Plainhttp://is accepted only for this machine. pack synthandupgradeask for the project's page (the library's name is in the address) and the files of the releases they compare.outdatedasks, for each distribution your manifests declare, for its project page and for the index's JSON metadata of the newest release and of the release you use, so the name of every dependency you declare, a private one included, and the release in use are in the addresses it requests, so point it only at an index that may know them. The files come from the host the page names, which for PyPI isfiles.pythonhosted.org. None of them sends a cookie, a body, nothing from your repository and nothing about your machine beyond aUser-Agentof the formobelize/<version>and what any HTTPS request carries. AnAuthorizationvalue inOBELIZE_INDEX_AUTHORIZATIONgoes to the index host and to none other, and a redirect to another host is refused.- Each host is printed on standard error before it is contacted, once.
- Answers are kept for you alone in
$XDG_CACHE_HOME/obelizeor~/.cache/obelize: a project page for an hour, a file for good, named by its hash.--offlineornetwork.offline: truereaches no host and uses only what is kept. Delete the directory to forget them. - A proxy named in your environment is honoured, as for any tool of yours: this is your network, not a model's.
What obelize upgrade --apply installs¶
Before it writes a move, upgrade --apply tries it: it copies the repository, installs its
dependencies twice with uv (once with the release in use, once with the release moved to) and runs
its tests in each. The copy and the environments are made in $XDG_CACHE_HOME/obelize/trials (or
~/.cache/obelize/trials) and removed when the trial ends; what stays is its evidence, in
.obelize/trials/<id>/ of the repository: the junit files and output of each test run, what was
installed in each environment, and the verdict. Installing reaches the index uv is configured for
(UV_INDEX_URL and the like, not network.index) and runs the build scripts of what it installs,
and with --offline, network.offline or OBELIZE_OFFLINE it is kept to uv's own cache; the tests
run the code of the repository. Nothing about the repository or its tests is sent
anywhere by obelize. A trial happens only if you pass --yes or answer yes at a terminal.
What an agent run sends¶
With --agent claude_code (or agent.backend in your own configuration), upgrade --apply --branch
asks your claude program about the places the pack flags, one question each, up to --max-asks
(50) a round and up to --max-rounds (3) rounds for the tests that broke after the move. A question is the same text the model adapter sends: the pack's id and target package, the
file's path, the line and symbol, the pack's own words about the place, the new names closest to a
removed one where pack synth kept them in hints.json, and the lines around it (redacted by the
same redactor, numbered), and nothing else of the repository. It goes to wherever
your claude login sends a prompt, which is not obelize's to say and not a host obelize prints.
The program is started in an empty directory with every tool off, no session saved, no MCP server,
no skill and none of your settings, and with a short list of variables: PATH, HOME, the proxy and
certificate variables of your network, and any beginning ANTHROPIC_, CLAUDE_, AWS_, GOOGLE_
or VERTEX_, which are what a login to Claude through Anthropic, Bedrock or Vertex uses. A token
for GitHub or a package index is not among them. Its answers are checked
by the guard and written only to the trial's copy. For a test that broke, the question holds the
test's name and the last line of its error (redacted and cut to 300 characters) in place of the
pack's words, and a test that broke is never itself the place to edit. A place the pack flags in a
test file is asked about like any other, and the commit message names every file the agent edited.
--agent needs --branch, so nothing a model
helped write reaches your working tree before you have read it on a branch.
What changes with the model adapter¶
One optional model adapter can be asked about cases the rules refuse. It is off by default
(model.provider: none), and then nothing above changes.
When you configure it:
- One endpoint, and it is yours: the
base_urlin your own~/.config/obelize/config.yml, such as a local Ollama server, your own OpenAI-compatible gateway, or a provider you have an account with. Obelize has no endpoint of its own, and amodel:block in a repository's.obelize.ymlis refused with exit2before anything is sent, so the code being migrated cannot choose where it, and your key, go. Plainhttp://is accepted only forlocalhost,127.0.0.0/8and::1; any other host needshttps://, ormodel.allow_insecure_http: truein that file, since the key and your source would cross the network in the clear. - Only the relevant slice of code is sent: the smallest function or class holding the whole binding group of the ambiguous call site, else that group with 20 lines either side, and nothing when neither fits in 80 lines. With it go the pack's id and target package, the file's path, the line and symbol of the call, the reason the rules refused and the pack's limitations. Never the rest of the repository or your git history, but a file short enough for the slice to reach both of its ends is sent whole.
- The slice comes only from the Python file the finding is in, and is redacted before the payload is built. Each of its lines is sent behind its line number in the file, so the model need not count them.
- One host, and nothing helping you reach it: a proxy named in your environment is not honoured and a redirect is refused, since either would carry the payload and the API key to a host you did not configure.
- The request identifies itself as
obelize/<version>, and says nothing else about your machine. - The host is printed before the run starts, on stderr.
--show-contextprints every string the payload carries, and sends nothing; it works with no provider configured.- The API key never appears anywhere: it is read from the environment variable named by
model.api_key_env(defaultOBELIZE_MODEL_API_KEY) and never written to configuration, logs, evidence or prompts. - Raw prompts are not stored:
model/proposals-<n>.jsonrecords the context range, a prompt sha256, the provider, the model, token counts, the proposal and whether it passed validation. The proposal quotes the lines it replaces, so those lines of your source, as redacted when they were sent, are in it. The request body is written only if you setmodel.log_prompts: true, because a prompt is your source code. - A proposal is written only under
--applyand--accept-model, and is still listed as a review item.
Once a payload leaves your machine, the endpoint operator's terms govern it, not Obelize. If
that matters for your code, point base_url at a local model.