GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — Tags · agenticarus/patch
Tags
v0.2.0## [0.2.0] - 2026-08-31
### Added
- `--command-timeout SECONDS`, which bounds how long a command run by `/run`, `/test` or
`--test-cmd` may take, with `0` meaning no limit. The 120-second default introduced with command
timeouts had no escape hatch, and a suite longer than that is ordinary -- this repository's own
takes about 150 seconds, so `patch --test-cmd pytest` here was killed at 120s and, under
`--auto-test`, the timeout was handed to the model as a test failure to go and fix. The setting
carries across a coder switch and applies to the linter as well.
- A fourth full-repository audit in `AUDIT_REPORT.md`, split into independent security,
correctness/reliability, performance/concurrency, and maintainability/testing/packaging/docs
passes. It records nine evidence-backed findings (F-98 – F-106), and the candidates each
pass investigated and rejected.
- A release process in `CONTRIBUTING.md`. The version comes from the git tag rather than a file, so
an untagged build carries the `+g<sha>` local segment that PyPI refuses; the steps cover cutting
the changelog section, tagging, building, and publishing.
- A workflow section in `AGENTS.md` requiring each task to land as one commit that carries its own
doc updates, so documentation cannot drift behind the code. It also states when a released
changelog section may be corrected, and that the GitHub release body is updated alongside it.
- `.github/workflows/packaging.yml`, which builds a wheel from a `.git`-less copy of the working
tree, installs it into a clean environment, imports it from another directory and checks the
console script and the bundled data files. The test suite runs from the repository root, where
`tests/__init__.py` places the working tree on `sys.path`, so `import patch` resolved to the source
checkout and no test ever exercised the artifact users install.
- A second full-repository audit in `AUDIT_REPORT.md`, performed after the first audit's findings
were all resolved. It records 35 findings (F-42 – F-76), 4 of them Critical, each verified by
executing the code. Three of the four are fixed in this release and listed below; the fourth,
`set-env` in a repository config bypassing the environment denylist, is still open.
- A third full-repository audit in `AUDIT_REPORT.md`, performed after the first two rounds were
fully remediated. It records 21 findings (F-77 – F-97) — one Critical, three High — tracked as
and all fixed in this release. It also adds a "Disproved during this audit" section recording
seven candidate findings that were attacked and discarded, so a fourth round does not re-derive
them.
- `AUDIT_PROMPT.md`, the reusable brief for a full-repository audit. Three rounds have been run and
each had to rediscover the same process lessons, so the file carries them: verify Critical and
High findings by executing the tree's own code rather than by reading; search for the shape every
Critical finding across three rounds has had (a guard that exists, is right, and is missing from
one call site); distrust gates that are greener than their coverage; and record what you disprove.
It includes a table of seven candidates already investigated and rejected, the current baseline so
a regression in the gates is itself a finding, and the report and issue formats the existing
rounds use.
- Regression tests for the findings whose absence was the actual root cause: a repository config
naming its own env file, the `HOME`-is-the-repository collapse, a failing summarizer leaving the
history intact, atomic writes surviving a failed write, and an unrunnable lint command staying out
of the lint results.
### Changed
- **`check_untyped_defs` is on by default**, with an explicit three-module exception list that is
meant to shrink (`base_coder.py`, `models.py`, `io.py`). Without it mypy does not analyse the body
of an unannotated function at all, so most of the 29 gated modules were unchecked. Turning it on
reported 97 errors across 12 modules, six of which were real latent defects rather than annotation
noise — see Fixed above. 26 of the 29 are now clean under it.
- **State carried across a coder switch is a declared table**, `Coder.CARRIED_OVER`, rather than a
dict literal written out by hand in `Coder.create`. Forwarding by hand is how a setting gets
silently dropped on `/ask` or `/model`, as the voice settings once were. A test now asserts
every name in the table survives a real switch, and that each is a constructor parameter.
- **The repository-trust policy is a module, not sixteen functions scattered through `main()`.**
`patch/repo_trust.py` owns the denylists, the path checks and the three layers, and `RepoTrust`
applies them in a fixed order — `sanitize_args` raises `TrustOrderError` if it runs before the
`.env` layer, since argparse reads `PATCH_*` variables between them and that ordering was
previously an unwritten property of the statement sequence. `RepoTrust.command_line_file` is now
the only way to ask "did the user name this file, or did the repository?", which is the question
the `env-file` bypass got wrong. The denylist is also split by *why* each key is denied
(execution, credentials, file-naming, self-trust) rather than being one flat set.
- **The trust suite asserts the policy over the whole argument surface**, not one key at a time. A
new test enumerates `parser._actions` and fails on any argument that names a file or a command
and has no recorded decision — either on the denylist, or in `REVIEWED_SAFE_KEYS` with the reason
it is safe. Removing `env-file` from the denylist now fails that test by name, which is what the
bypass above needed and did not have.
- **`main()` is 411 lines rather than 777**, with eight pieces extracted that can be called from a
test without constructing a session: `resolve_git_root`, `config_search_path`, `build_config`,
`apply_theme_settings`, `apply_env_settings`, `setup_analytics`, `resolve_input_files`,
`setup_model`, `run_one_shot_modes` and `run_repl`. Behaviour is unchanged; the two parse passes
and the trust layers between them now live in `build_config`, where their order is stated.
- **Token/cost accounting and the chat transcript moved out of `Coder`** into
`coders/usage.py` (`UsageTracker`) and `coders/chat_state.py` (`ChatState`). The per-provider
cache-pricing arithmetic and the summariser's sentinel contract — a failed summary must never
replace the transcript — are each testable on their own now. `Coder` keeps its
existing surface through delegating properties, so the ~480 places the suite touches these
counters are unchanged.
- **`cmd_add`'s refusal rules are a named method**, `reason_to_refuse_add`, rather than two compound
conditions fifty lines into a hundred-line method — which is how the containment check came to be
gated on `--auto-commits`.
- The mypy gate covers **29 modules** rather than ten. Widening it again immediately caught an
implicit `Optional` in `waiting.step`, the second real defect the last two widenings have found.
`patch/repo_trust.py` is at 93% test coverage.
- The mypy gate covers ten modules rather than three, adding `exceptions.py`, `history.py`,
`linter.py`, `report.py`, `run_cmd.py`, `utils.py` and `watch.py`. Widening it immediately caught
`ExInfo`, whose `name`, `retry` and `description` were annotated as required while most entries
pass `None` and `get_ex_info` returns `ExInfo(None, None, None)`.
- CI reports test coverage (`--cov=patch --cov-report=term-missing`) on the Ubuntu job. It is
reported, not gated: a threshold now would either assert nothing or fail the build on unrelated
changes. The workflows also no longer key their pip cache on `requirements-dev.txt`, which no job
installs.
- `get_file_mentions` builds its normalized word set once instead of once per repository file. The
set is loop-invariant and this runs after every model reply, costing ~0.4s per reply on a
20k-file repository against ~0.001s now.
- The repo-map tag cache is read once per hit rather than twice. `get_tags` already held the entry
and then looked it up again, paying a second diskcache read and unpickle for every tracked file on
the warm path.
- `importlib-metadata` is no longer capped in the runtime dependencies. The cap existed to work
around a `twine` bug in release tooling, but it applied to everyone who installed `patch-chat`,
holding a June-2024 version that predates the Python 3.13 and 3.14 this project supports. It now
resolves to 9.0.1.
- The lint and type gate runs on a pinned Python 3.10 rather than whatever the CI image ships, so
mypy is checked against the version `pyproject.toml` configures. This also populates
`env.pythonLocation`, which the hook cache keys interpolate — unset, they collapsed to an empty
segment and restored hook environments built against a different interpreter.
- `scripts/pip-compile.sh` forwards all its arguments to `uv pip compile`, not just the first, so
flags that take a value such as `--upgrade-package <name>` work.
- Dropped `pip-tools` from the `dev` extra (the compile script uses `uv`), the unreachable
`tree-sitter` marker for Python below 3.10, and `--extra-index-url` from `.agents/setup`, which
resolved across both PyPI and the torch index rather than replacing it.
- The mypy gate now checks something. `follow_imports` was `"skip"`, which resolved `patch.io` and
`patch.models` to `Any` even though both were listed for checking, so nothing crossing a module
boundary was verified; it is now `"silent"`, and `Coder.io` is annotated so the IO boundary is
actually typed. `check_untyped_defs` stays off — it reports 69 errors that are predominantly
noise.
- Installation now leads with `pip install patch-chat` from PyPI instead of a source checkout, which
is kept as the contributor path. `README.md` gains PyPI version and supported-Python badges, and
`AGENTS.md` records that versions come from git tags and that the fork numbers from `0.1.0`.
### Security
- Repository-controlled `.env` and `set-env` can no longer replace the interactive command shell
or inject shell startup files, and repository config can no longer redirect chat, input, LLM or
analytics logs to arbitrary writable paths. Explicit command-line and trusted home settings keep
working.
- A repository can no longer grant itself trust by naming its own environment file. `env-file` was
the one key missing from the config denylist, so `env-file:./evil.env` in a repo's
`.patch.conf.yml` made that file the "explicitly named" one `load_dotenv_files` trusts — loading
it with `override=True` and no filtering. Because the parser uses `auto_env_var_prefix="PATCH_"`,
the file could then set `PATCH_TEST_CMD`, which `sanitize_repo_config` cannot strip: it reads yaml
files rather than the environment, and runs after the `.env` is loaded. `git clone && patch` was
enough to reach arbitrary command execution and redirect every prompt and API key.
Only a `--env-file` actually present in `argv` now confers trust.
- The Docker images no longer set `HOME` to the directory the project is mounted into. The whole
repository-trust boundary is a comparison against `Path.home()`, and `ENV HOME=/app` — where
`/app` is also `WORKDIR` and the bind-mount target — made the repository's own `.patch.conf.yml`
and `.env` indistinguishable from the user's, leaving every filter inert in exactly the
environment people use to run an agent against unreviewed code. It also put the pickle-backed
repo-map cache back inside the repository under edit. `HOME` is now `/home/appuser`, and
`is_trusted_config_path` additionally fails closed if it is ever told that home and the git root
are the same directory.
- Provider exceptions are no longer sent verbatim to analytics. `message_send_exception` forwarded
the full `str(err)`, which for litellm routinely carries the request URL, `api_base` and
occasionally prompt fragments — contradicting the opt-in prompt's promise that Patch "never
collects your code, chat messages, keys or personal info". Only the exception class name is sent;
the full text is still shown locally.
- `/add` no longer stops checking containment when `--auto-commits` is off. The guard against adding
files from outside the repository root was `and self.coder.auto_commits`, a flag with no bearing
on where a path is, so `--no-auto-commits` disabled it entirely and out-of-root file contents were
sent to the model as chat context.
- The crash reporter redacts credentials found in the traceback. Values of `*_API_KEY`, `*_TOKEN`,
`*_SECRET`, `*_PASSWORD` and `*_ACCESS_KEY` environment variables are replaced before the issue
body is composed; previously only file paths were scrubbed, and an exception message quoting the
failed request could carry a key into a prefilled browser URL.
- The OpenRouter OAuth key is no longer written world-readable. `~/.patch` is created `0700` and
`oauth-keys.env` `0600`; under the usual umask they were `0755` and `0644`, leaving a live billing
credential readable by every local account — and because `main.py` treats that file as trusted
configuration, writable by them too.
- Scraping an HTML page no longer downloads and installs a pandoc binary without asking. The install
ran unprompted and unverified, reached from ordinary chat input via URL detection, while every
other third-party install in Patch is gated behind a confirmation.
- The repo-map tag cache moved out of the repository, to
`~/.patch/caches/tags.v<N>/<hash of the repo path>`. It was kept in
`.patch.tags.cache.v<N>/` inside the repository being edited, and `diskcache` stores its values as
pickles — so a repository that committed a crafted cache had it deserialized during an ordinary
repo-map build, with no user interaction, on any checkout path an attacker could predict (CI
workspaces and containers are fixed and public). Existing `.patch.tags.cache.v*` directories are
no longer read and can be deleted.
- A repository's `.patch.conf.yml` can no longer set the environment variables its `.env` is
forbidden to set. `set-env` was absent from the untrusted-config denylist, so a repository could
set `OPENAI_API_BASE`, `HTTPS_PROXY`, `SSL_CERT_FILE` or `REQUESTS_CA_BUNDLE` through it and
achieve the redirection the `.env` layer exists to prevent. `set-env` from an untrusted config is
now filtered the same way a repository `.env` is — dangerous names dropped, ordinary ones such as
`PATCH_MODEL=gpt-4o` kept — and `api-key`, `openai-api-base`, `openai-api-type`,
`openai-api-version`, `openai-api-deployment-id`, `analytics-posthog-host`,
`analytics-posthog-project-api-key`, `model-settings-file` and `model-metadata-file` are refused
outright.
- The trust check on model settings files no longer treats an argparse default as a deliberate
choice. `--model-settings-file` defaults to the bare filename, which resolves against the
repository being edited, so the default was being honoured as "a file the user named" — which
would have let a cloned repository supply model settings despite the fix above. Whether the user
named a file is now decided from the command line itself.
- A repository can no longer redirect your requests through its own
`.patch.model.settings.yml`. That file was loaded from the git root and working directory with no
trust check at all, and its `extra_params` are passed straight to litellm — so a cloned repository
could set `api_base`, `api_key` and `extra_headers` for *every* model and receive each prompt, the
repo map, the contents of every file in the chat, and the header carrying your real API key. The
reserved `patch/extra_params` entry applies to all models, so no knowledge of your setup was
needed. Both this file and `.patch.model.metadata.json` now follow the same rule as
`.patch.conf.yml` and `.env`: only your home-directory copy and a file you name with
`--model-settings-file` / `--model-metadata-file` are trusted, ignored files are reported, and
`--trust-repo-config` opts back in.
- Files added with `--read` or `/read-only` can no longer be written. `allowed_to_edit` never
consulted the read-only set, and `path_within_root` returned `True` for any path in it — so the
model could overwrite a reference file, and a read-only file outside the repository was a way
through the containment boundary entirely under `--no-git`. Reading such files is unaffected;
neither caller of `path_within_root` is on a read path.
### Removed
- The `mixpanel` runtime dependency and the dead analytics path it served. The only use was
importing `MixpanelException`; the construction that would populate `self.mp` has been commented
out, so the attribute was permanently `None` and the tracking block unreachable.
- Roughly 440 unreachable lines from `patch/coders/search_replace.py` (757 → 309): the
`editblock_strategies` and `udiff_strategies` tables that nothing imports — `udiff_coder` builds
its own list containing only `search_and_replace` — and with them `git_cherry_pick_osr_onto_o`,
`git_cherry_pick_sr_onto_so`, `dmp_apply`, `dmp_lines_apply`, `map_patches`, `line_pad`,
`line_unpad`, `lines_to_chars` and the module's benchmark harness. `diff-match-patch` remains a
dependency: the live `diff_lines` uses it.
- `watch.py:main`, which was broken rather than merely unused — it passed a directory string where
`FileWatcher` requires a coder and immediately dereferences `coder.io`, then called a
`get_changes()` that does not exist.
- A `dump(edits)` debug statement in `prepare_to_edit`, on the production write path, and the
`sys.version_info >= (3, 10)` compatibility branch in `IgnorantTemporaryDirectory` — the project
requires 3.10.
- PyTorch from the `patch-full` Docker image. Nothing in the package imports it and it is in no
requirements file; it cost several hundred megabytes and a long build step.
- `scripts/versionbump.py`, inherited from aider and left behind when versioning moved to git tags.
It rewrote the `__version__` assignment in `patch/__init__.py` -- which is now the `safe_version`
floor rather than the version -- left `CHANGELOG.md` and `pyproject.toml`'s `fallback_version`
untouched, and pushed a second `vX.Y.Z+1.dev` tag that setuptools-scm would then have derived
every later build from. Nothing referenced it, and `CONTRIBUTING.md` documents the release steps
that replaced it.
### Fixed
- **The documented release command stripped every heading from the tag message.** `git tag -F`
defaults to `--cleanup=strip`, which deletes each line beginning with `#`, so feeding it a
changelog section dropped the `## [X.Y.Z]` title and all the `###` groups and left an
undifferentiated wall of bullets. `CONTRIBUTING.md` now passes `--cleanup=verbatim`, and writes
the section to a file that the later `gh release create --notes-file` reuses -- the previous
`<release notes>` placeholder was unusable in PowerShell, where `<` is a reserved operator.
- **`patch --gui` disabled its file watcher in a development checkout.** The dev-version test
looked for `-dev` in the version string, but setuptools-scm spells the marker `.dev` (PEP 440),
so it never matched and streamlit was always started in the packaged-release configuration. A
stale comment above it linked to an upstream aider issue number rewritten to point at this
repository, where no such issue exists.
- **Stopping a command crashed on Windows.** `_stop_process_tree` called
`isinstance(process, pexpect.spawn)`, but `pexpect` does not define `spawn` on Windows at all --
the attribute is absent, so that raised `AttributeError` rather than returning `False`. Every path
that stops a command went through it, so a timeout returned exit status 1 with the AttributeError
text instead of `TIMEOUT_EXIT_STATUS`, because the outer `except Exception` swallowed it. The same
shape applied to `os.killpg`, which is also POSIX-only. Both are now resolved once at import and
tested for directly, which says what the code needs rather than inferring it from the platform
name.
- Failed or empty `--message-file` input now exits nonzero instead of reporting success or entering
the REPL; OpenRouter OAuth always releases its callback thread and port; and failed editor temp
writes preserve their original error without leaking partial files.
- Shell commands now time out after 120 seconds with status 124, terminate descendants and retain a
bounded output tail while still streaming everything to the terminal. Repo-map memoization now
uses a 16-entry LRU and skips modes that cannot reuse it.
- Built package metadata now carries the SPDX `Apache-2.0` license expression and packaging CI
verifies it, the bundled license and the Python requirement. Installation docs now state the
supported Python 3.10–3.14 range.
- **`compile()` raising without a `lineno` no longer crashes the linter.** `lint_python_compile`
read `err.lineno` off a bare `Exception`; `compile()` raises `SyntaxError`, which carries it, but
also `ValueError` for source containing null bytes, which does not — turning a lintable file into
an `AttributeError` from inside the linter.
- **`dump()` no longer raises when the caller's source is unavailable.** It read
`traceback.extract_stack[-2][3]`, which is `None` for exec'd code, a frozen build, or a `.pyc`
without its `.py`.
- **The crash handler restores the default excepthook instead of setting it to `None`.** CPython
prints "sys.excepthook is missing" and loses the traceback when the hook is `None`, which is the
opposite of what that line intended.
- Containment is re-checked **at the moment of writing**, not only when the edit is planned.
`allowed_to_edit` runs during `prepare_to_edit` and the writes happen later in `apply_edits`, so a
lint command, a test command or the file watcher could replace a checked path with a symlink
pointing outside the root in between. Every coder now writes through
`Coder.write_edited_file`, and a test asserts none of them calls `io.write_text` directly.
- `/run` no longer behaves differently depending on whether stdin is a tty. The pexpect path spawned
`$SHELL -i`, sourcing the user's rc files — aliases, functions, `PATH` edits — while the
subprocess path used `/bin/sh` with none of that, so the same `--test-cmd` could pass
interactively and fail in CI.
- Commit attribution no longer mutates `os.environ` process-wide. `GIT_COMMITTER_NAME` and
`GIT_AUTHOR_NAME` are scoped to the git subprocess via `custom_environment`, so the watcher and
cache-warming threads no longer see them and a crash cannot leave them set. Reading `user.name`
from a repo that has none is also guarded, rather than raising out of `commit()`.
- The non-atomic write fallback warns instead of being silent. On Windows, when another process
holds a file open, `os.replace` is refused and the write falls back in place — the one remaining
path where an interrupted write can truncate a file. The user is now told which file, and why.
- `run_install` returned a list rather than the joined output string on the success path.
- A failed history summarization no longer discards the entire conversation. `summarize_all` raises
`ValueError` whenever every model fails — a transient provider outage is enough — and the worker
caught it, leaving `summarized_done_messages` as the empty list the previous `summarize_end` had
reset. `summarize_end` then assigned that over `done_messages`, silently wiping the chat with only
a one-line warning, after which the model continued with no memory of the session. The result is
now a `None` sentinel that `summarize_end` refuses to adopt, and unexpected exceptions in the
daemon thread are reported rather than escaping as a raw traceback under the prompt.
- A new litellm release can no longer break every LLM call. `LiteLLMExceptions._load` raised
`ValueError` for any `*Error` class not already in the hard-coded table, and `send()` builds the
registry *inside* its `except` clause — so the raise replaced the real API error with a message
about Patch's exception list. Unknown classes are now recorded rather than raised (the `strict`
flag that was never read now gates that), and the registry is built once instead of on every error
and every retry.
- Edits already written to disk are no longer lost when a later one fails. Only the `ValueError`
branch of `apply_updates` reported partial progress; the `OSError`, git-error and general branches
returned an empty set, so `send_message` skipped `auto_commit` for files that had already changed
— never committed, never linted, and with no commit for `/undo` to revert. Applied paths are now
tracked on the coder, so every failure path reports them.
- Files are written through a temporary file and renamed into place, instead of being truncated and
rewritten. A crash, Ctrl-C or full disk mid-write left the user's source file empty or partial.
Where the rename is refused because another process holds the file open — a Windows-only case —
it falls back to the previous in-place write rather than failing.
- A lint command that cannot be run is reported as such, rather than as lint errors for the model to
fix. `run_cmd_subprocess` flattened every exception into `(1, str(e))`, so a missing binary was
indistinguishable from a linter reporting problems, and `[Errno 2] No such file or directory` was
fed back as something to correct. It now re-raises `OSError`, which makes the handlers in both
callers reachable again.
- `simple_send_with_retries` no longer swallows every `AttributeError` in its body, which turned any
typo or API-shape change into a silent `None` — the same `None` the summarizer reads as "this
model failed", hiding the cause of the history loss above. Its errors and retry notices also go
through `io` instead of bare `print`, which wrote past the display while a spinner was running
during commit-message generation.
- `read_image` reports "file not found" and "is a directory" again. Both handlers sat after
`except OSError`, of which they are subclasses, so neither could ever run.
- `RepoMap.warned_files` is per instance rather than shared by the class, so the "repo-map can't
include" warning is not suppressed for the rest of the process after one coder switch, and the set
no longer grows without bound.
- `PATCH_CACHE_KEEPALIVE_DELAY` is parsed defensively and clamped to at least one second. A
non-numeric value raised `ValueError` straight out of the request path, and `0` billed a
completion every second.
- A redundant `nonlocal server_error` in the OAuth callback handler, which only ever assigns
`auth_code`. Current flake8 reports it as F824; the pinned 7.1.0 does not, so the next routine
`pre-commit autoupdate` would have failed the build for an unrelated reason.
- The test suite no longer reads or writes the developer's own state. The model-info singleton is
built at import time and had captured the real `~/.patch/caches`, so results depended on whether
that cache happened to be fresh and a blocked fetch could overwrite it with `{}`; the real `git`
binary read the developer's global config, letting `core.hooksPath`, `commit.gpgsign` and
`core.autocrlf` change test behaviour; a `webbrowser` patcher was never stopped; and several tests
cleared the live process environment. The commit-attribution tests now configure a real `GitRepo`
instead of a mock whose `args` attribute no production code sets, and consequently run on Windows
as well.
- `--notifications` no longer blocks the prompt on Windows. The default was a modal
`MessageBox::Show`, which returns only when the dialog is dismissed, run from the synchronous path
before every input — so Patch stopped until the user clicked OK, while the macOS and Linux
commands return immediately. It is now a non-blocking toast, and any notification command is given
a 5-second timeout so a hanging one cannot wedge the REPL on any platform.
- `--yes-always` no longer opens URLs automatically. The offered URLs are extracted from provider
error text, which a hostile or compromised endpoint controls, so this is now an explicit-yes
prompt — matching how LLM-suggested shell commands are already treated.
- A terminal encoding failure at the prompt reports one line instead of a traceback; its handler sat
after `except Exception` and could never run.
- `--voice-format` and `--voice-input-device` survive a mode switch. `Commands.clone()` runs on
every switch (`/ask`, `/code`, `/architect`, `/model`, `/chat-mode`) and did not forward them, so
after the first one `/voice` recorded from the default microphone as wav.
- `/paste` no longer leaves a temporary directory behind for every image. Each paste created a fresh
one that was never removed, and outlived the process; there is now one per session, cleaned up at
exit.
- A `patch`-format body can no longer make Patch read files outside the repository while parsing.
Writes were already contained, but the context files a patch names were read first, with no
equivalent check.
- The clipboard watcher reports failures through the normal output channel instead of a debug
printer that wrote raw to stdout underneath the prompt, and the unused `dump` import is gone from
22 modules.
- Whitespace-only lines in a unified diff keep their line ending. `cleanup_pure_whitespace_lines`
indexed where it meant to slice, so a CRLF blank line collapsed to a lone carriage return before
every hunk was matched.
- `/run` and `/test` no longer crawl through large output. The loop read one byte at a time and
flushed a write for each, which cost about 28x: 2 MB of output took 1.42s to consume and now takes
0.17s. Reading by line changes nothing about what is captured.
- On Windows, `/run` and `/test` no longer mangle commands containing `|`, `&`, `>`, `^` or quotes.
When the parent shell was PowerShell, `powershell -Command ` was spliced onto the front of a
string that was then handed to `cmd.exe`, which consumed those characters before PowerShell saw
them — so the same command behaved differently than on POSIX. PowerShell is now invoked as an
argument list.
- Optional provider SDKs are no longer installed on the strength of an object that is not a model
name. `check_for_dependencies` ran `startswith("bedrock/")` on whatever it was handed, which is
truthy for a stand-in, so a `boto3` install could be triggered for a model that was never a
Bedrock model. CI has been failing on this since the checks were first enabled.
- `from patch import *`, `from patch.llm import *` and `from patch.coders import *` work. All three
`__all__` lists held objects rather than names, so the first raised `ModuleNotFoundError` for a
submodule named `0` (it exported the version *value*) and the last raised
`TypeError: Item in __all__ must be str, not type`. The edit-format registry that scanned
`patch.coders.__all__` now has its own explicit `ALL_CODERS` list.
- A file change or clipboard paste arriving as the prompt starts no longer discards what you were
typing or stops `--watch-files` working. The watchers are started immediately before the prompt,
and interrupting an application that is not yet running raises — which killed the file-watcher
thread outright, and left an "interrupted" flag set that made the next prompt throw away the line
you had actually typed. The interrupt is now dispatched on the application's own event loop and
only takes effect once accepted, and a file watcher that does stop says so instead of vanishing.
- Completions no longer break when typing quickly. Each keystroke runs the completer on its own
thread, and the shared word list was both latched as "ready" before it had been filled and then
iterated while another thread was still writing to it — raising out of the completion thread and
leaving completions empty or partial for the rest of the prompt.
- `--cache-keepalive-pings` now actually sends its pings. The worker built its request arguments
with `dict(extra_params)`, which raises `TypeError` for the usual case of no `extra_params` — and
it ran in a daemon thread, ahead of the `try` that would have reported it, so the thread died
silently. Prompt caching went unwarmed and full uncached prices were paid, with nothing to
indicate it.
- `--model-metadata-file` overrides are no longer discarded. Context windows and pricing you supply
were returned only while litellm had not yet been imported; once anything touched it — the normal
case — litellm's own table won instead. Local and custom models silently reported the wrong
context limit and `$0.00` costs.
- Reasoning and thinking-token settings no longer leak between models. A `patch/extra_params` entry
in your model settings applies to every model, and the merge assigned its nested dictionaries by
reference; `--reasoning-effort` and `--thinking-tokens` then mutated those dictionaries in place,
writing back into the shared table. Every model built afterwards in the same process inherited the
settings — the weak model, the editor model, and anything switched to with `/model` — sending
reasoning parameters, and their cost, to models never configured for them.
- An edit that empties a file is no longer reported to the model as a failed match. A SEARCH block
covering a whole file with an empty REPLACE — truncating it — produced `""`, which every gate in
the edit chain tested for truthiness and so could not distinguish from "no match found". The model
was told `SearchReplaceNoExactMatch` and retried an edit that had already succeeded, and the
response was counted against the malformed-response limit. The same applied to a unified-diff hunk
that empties a file.
- Malformed or truncated model output no longer raises Python exceptions out of the edit parsers.
Three cases, each of which surfaced as a traceback and was reflected back to the model as a raw
interpreter message: a unified diff whose first hunk has no `--- `/`+++ ` header (`TypeError`; the
`udiff-simple` format never asks for those headers except on a new file), a response cut off
directly after a ` ```diff ` fence (`UnboundLocalError`, losing the entire response), and an empty
SEARCH section (`IndexError`, which also discarded the record of edits already written to disk, so
they were neither committed nor linted). An unterminated fence with content also no longer drops
its last line.
- A wheel built from a source tree with no `.git` no longer omits `model-settings.yml`.
`[tool.setuptools.package-data]` enumerated only `*.scm` and `*.json`; everything else reached the
wheel through setuptools-scm's git file finder, which is unavailable in a source export or the
Docker build context (`.dockerignore` excludes `.git`). `patch/models.py` opens that file at import
time, so both Docker images built an entrypoint that raised `FileNotFoundError` on every
invocation — while passing the entire test suite.
- `--dry-run` no longer commits your uncommitted work. Editing a file with unstaged changes
triggered a pre-edit commit of that file, because `dirty_commit` did not make the `--dry-run`
check that `auto_commit` already made — so a run intended to preview changes rewrote git history
and spent an LLM call writing the commit message. The file edit itself was already correctly
skipped.
- The editor and web-scraping tests no longer depend on the machine they run on. `test_pipe_editor`
asserted the Unix default editor, so it failed on Windows and for anyone with `VISUAL`/`EDITOR`
set, and the scrape tests reached `try_pandoc()`, which looked for a pandoc install under the
temporary home directory and then tripped the suite's network guard trying to download one.
v0.1.0Patch's first release. This fork of aider starts its own version hist…
…ory at `0.1.0` rather
than continuing the upstream `0.86.x` numbering.
- `--trust-repo-config` (`PATCH_TRUST_REPO_CONFIG`) to opt back into execution-capable settings
and endpoint/credential environment variables from a repository's own `.patch.conf.yml` and
`.env`. Accepted only from the command line or your shell environment, and itself on the
ignored list, so a repository cannot grant itself trust.
- `AGENTS.md`, a guide for AI coding agents working in this repository: setup and test commands,
the black/isort/flake8/mypy/codespell rules CI enforces, a map of `patch/coders/` and the other
main modules, and the generated files that must not be hand-edited.
- Renamed the Python package, imports, command, configuration files, environment variables,
caches, Git attribution and distribution metadata from the previous identity to Patch.
- Simplified `/help` to show command help and link to the Patch website without bundling a
second documentation application and its large optional dependency set.
- Completed the user-facing Patch rebrand across terminal and browser UI copy, notifications,
onboarding, package metadata, and issue reporting.
- Rebranded `README.md` as Patch: replaced the previous logo, screencast and badges, and rewrote the
feature and installation sections.
- Documented the write boundary and the repository trust model in the README.
- Trimmed the Windows CI test matrix from five Python versions to `3.10, 3.13`. Ubuntu CI covers
every declared version, `3.10` through `3.14`.
- The bundled Jekyll website, its build/update scripts, historical release archive, interactive
help indexer, and website-only benchmark reporting utilities.
- Legacy GitHub Actions workflows that depended on external secrets, package registries and
domains not owned by Patch:
- `check_pypi_version.yml` and `windows_check_pypi_version.yml` — daily cron jobs that installed
`patch-chat` from PyPI and compared it against this repo's git tags.
- `release.yml` — published the `patch-chat` package to PyPI.
- `docker-release.yml` and `docker-build-test.yml` — built and pushed `patch` / `patch-full`
images to Docker Hub. The build-test workflow also ran a multi-arch build on every push to
`main`.
- `issues.yml` — ran `scripts/issues.py --yes` against the issue tracker every 12 hours.
- `pages.yml` — built the `patch/website/` Jekyll docs and deployed them to `patch.chat`.
Remaining workflows: `pre-commit.yml`, `ubuntu-tests.yml`, `windows-tests.yml`.
- The root `CNAME` file, which pointed GitHub Pages at `patch.chat`.
- The `site/` landing page, which now lives in a separate project outside this repository.
- The test suite now clears ambient provider credentials and terminal color flags, uses a temporary
cross-platform home directory, blocks external socket connections, and mocks analytics and web
scraping clients. Tests no longer read user config files or depend on live services.
- Model instances now deep-copy mutable model settings before applying thinking-token and reasoning
overrides, preventing request parameters from leaking across model switches, weak/editor models,
and later sessions in the same process.
- GitHub Copilot token exchange now runs only for `github/` models, caches its token on the model,
and passes it directly to LiteLLM instead of parsing or overwriting the user's `OPENAI_API_KEY`.
- Repo-map query captures are now normalized once without duplicating each tag category's final
node or discarding all earlier nodes, restoring accurate reference counts and ranking inputs.
- Output-limit continuation is capped at eight additional requests and stops immediately if a
prefill-capable model returns no new content, preventing unbounded API spend and memory growth.
- Startup version checks and GitHub Copilot token exchange now use bounded HTTP connect/read
timeouts instead of potentially blocking forever.
- Edit failures caused by filesystem errors and unexpected programming exceptions are no longer
mistaken for Git errors or returned as successfully edited files, preventing automatic commits
of partially applied changes and ensuring the error is reflected back to the model.
- A failed SEARCH/REPLACE against a file that cannot be decoded now reports the normal match error
instead of crashing while trying to suggest similar lines.
- `--dry-run` no longer deletes files, removes move sources, or creates directories and empty files
when applying `patch` and unified-diff edits.
- Patch-format `Move to:` destinations now receive the same edit confirmation, gitignore, and
dirty-file checks as other write paths, and both sides of a move are included in commits and
`/undo` tracking.
- Builds from a source tree without `.git` no longer produce a wheel missing most of the package.
Only the top-level `patch` package was declared, so `patch.coders`, the `.scm` repo-map queries
and the bundled model metadata were pulled in incidentally by setuptools-scm reading the git
index. An sdist rebuild, source export or Docker context either failed on version detection or
produced a wheel with 48 files and no coders at all, which installed cleanly and then crashed on
first use. The same build now produces 288 files with everything present.
- Cached-token cost accounting no longer subtracts a per-token price from a token count, which had
inflated the reported cost of every cache-hit request.
- Unified-diff edits now report partial success correctly, so the model is no longer told to resend
hunks that already applied.
- All three edit formats now track which files were actually written on a partial failure and
return only those paths, instead of the `patch` coder aborting on the first failing action and the
other formats over- or under-reporting what landed on disk.
- `--line-endings platform` (the default) now preserves an existing file's LF or CRLF style instead
of rewriting every line to the platform's line ending on a single-line edit.
- Interrupted subprocesses (Ctrl-C during a lint/test command) are now terminated, reaped and have
their stdout closed instead of being leaked.
- The clipboard watcher no longer busy-spins when `pyperclip.paste()` fails, such as on a headless
or Wayland session.
- `/voice` recordings are now removed after use in both the success and failure paths, and audio is
streamed to disk instead of buffered unbounded in memory.
- The chat-summarizer thread no longer blocks process exit on Ctrl-C.
- Cloning a coder for `/lint` no longer permanently disables cache warming on the live coder.
- A malformed `.patch.model.settings.yml` or `.patch.model.metadata.json` now aborts startup with
the parse error instead of silently continuing with the wrong edit format, context window, or
pricing.
- Configuration precedence (home directory, git root, working directory) is now identical across
every parse pass, so the working directory reliably wins.
- The Docker image now pins its base image by digest, drops world-writable permissions and
redundant ownership changes, isolates the PyTorch CPU index from general dependency resolution,
and excludes `.git` from the build context.
- Unused runtime dependencies (`fastapi`, `orjson`, `socksio`, `backoff`) were dropped, imports used
directly (`dotenv`, `tqdm`, `pygments`) are now declared, and the universal lock generation script
preserves resolved pins and version markers instead of widening them into ranges.
- Tests can no longer install packages from PyPI mid-run; an autouse fixture rejects package-install
attempts, and browser tests mock the optional dependency checks they exercise.
- Mypy is now gated in pre-commit CI over `patch/io.py`, `patch/models.py`, and
`patch/coders/base_coder.py`.
- Image and PDF extensions are now matched case-insensitively, so uppercase filenames like
`photo.PNG` are no longer routed through text decoding.
- The configured editor is now invoked as an argv list without a shell, and its temporary file is
always cleaned up, including on launch failure.
- Web scraping now streams HTTP content under a 5 MB cap and checks serialized DOM size before
copying page content into Python, instead of materializing unbounded content.
- Spinner teardown no longer races between the worker and the caller thread, which had let a
`RecursionError` in the repo-map leave the terminal cursor hidden.
- PostHog's `on_error` callback now receives its actual arguments and detaches the failed client
instead of printing a wall of `X` characters that could corrupt terminal output.
- Removed unreachable fuzzy-match code, an unused parameter, a shadowed `ArchitectCoder` default,
and a redundant analytics instantiation.
- Ubuntu CI now covers every declared Python version (3.10 through 3.14), caches pip from both lock
files, installs `pytest-env` explicitly, and no longer skips tests when only workflow files change.
- New-file and append handling for edit-block and unified-diff edits now share one tested helper
instead of two independently drifting implementations.
- `--version` no longer reports a `0.86.3.dev+less` error sentinel, which this fork produced because
it carries no version tags.
- Analytics no longer enables PostHog's exception autocapture, which could transmit filesystem
paths and nearby source lines despite the consent message promising that code and personal data
are never collected.
- Command-line and verbose history logging now redacts keys passed through `--api-key`, secret
environment values passed through `--set-env`, and custom PostHog project keys, in addition to
the dedicated OpenAI and Anthropic options.
- Edits are now confined to the repository. Every path the model produces is canonically resolved
and must land inside the repository root before anything is created or modified; absolute paths,
`..` traversal and symlinks pointing out of the tree are refused and reported. Nothing checked
this before, so a path such as `../../.ssh/authorized_keys` was written, and an absolute path
replaced the root entirely — with no prompt under `--yes` and no barrier at all under `--no-git`.
Also closes two routes that bypassed even the incidental checks: the `patch` format's `Move to:`
target, written with no confirmation, and `/add`, which compared paths as strings and so accepted
`/work/repo-secret` while you worked in `/work/repo`.
Files outside the repository root can no longer be edited, even when passed on the command line.
Reference files added with `--read` are unaffected; they come from you rather than the model, and
are only ever read.
- A cloned repository can no longer configure Patch. `git clone && patch` was previously enough to
be attacked: a repository could ship `test: true` with `test-cmd: <payload>` to run a shell
command, or set `OPENAI_API_BASE` in its `.env` to send every prompt and every file in your chat
to someone else's endpoint. A `.patch.conf.yml` or `.env` found inside the repository can no
longer set:
- Config keys `test`, `test-cmd`, `lint`, `lint-cmd`, `auto-test`, `auto-lint`, `load`,
`notifications-command`, `editor`, `verify-ssl`, `git-commit-verify`, `trust-repo-config`.
- Environment variables ending in `_API_BASE`, `_BASE_URL`, `_API_URL` or `_ENDPOINT`, plus
`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, `REQUESTS_CA_BUNDLE`, `SSL_CERT_FILE`,
`CURL_CA_BUNDLE`, and the `PATCH_*` equivalents of the keys above.
Each ignored setting is reported at startup. Your home config, `~/.patch/oauth-keys.env`, and any
file named with `--config` or `--env-file` remain trusted; ordinary variables from a repository
`.env`, including `OPENAI_API_KEY` and `PATCH_MODEL`, still load normally; and anything passed on
the command line always wins.
|
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |