You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* fix(core): reject ambiguous fuzzy edits, skip unspawnable gates, bound child output (#955)
Three of the five #955 defects, each with the regression test its acceptance
criterion asks for.
editor: _match_fuzzy hardcoded match_count=1, so the ambiguity rejection every
other match level feeds (editor.py's match_count > 1 guard) was bypassed at the
one level where near-misses are expected. Two blocks scoring identically meant
the agent silently edited whichever came first. Windows that overlap are one
region seen at several offsets, so they still count once; separate regions that
tie are genuinely ambiguous and now say so.
gates: _run_pytest gated on shutil.which("uv"), which proves uv exists, not that
the target project has pytest. 'uv run pytest' then exits non-zero with 'Failed
to spawn' and the gate read FAILED -- 'CodeFRAME says my project is broken' when
the honest answer is 'unverifiable'. run_lint_on_file already drew this
distinction; the detection is now shared as _tool_is_missing.
subprocess_adapter: stdin=None *inherits* the parent's stdin, so a TTY-probing
CLI waits for input that never comes and burns the whole timeout having done
nothing -- DEVNULL answers the probe immediately. stdout/stderr were also
retained without bound, putting the driver's memory under the child's control;
both are capped now, keeping the tail (where an agent's conclusion is) and
saying when earlier output was dropped. stderr keeps draining past the cap, or
the child would deadlock on a full pipe -- the very thing the drain thread
exists to prevent.
Refs #955
* fix(kilocode): send the prompt over stdin, never argv (#955)
argv is world-readable. `ps` shows every element to every user on the machine,
so a prompt carrying the task description and whatever file excerpts were
assembled into it was on display for the duration of the run. It is bounded too:
Linux caps a single argv entry at 128 KiB, under CodeFrame's ~100K-token budget.
#1015 already routed *oversized* prompts to stdin and verified against 7.4.17
that `kilo run` with no positional reads the message from there. Size was never
the whole problem, so that path is now the only modern path -- which also
retires _prompt_exceeds_argv and the size branch it fed.
Legacy 0.22.0 is untouched: it has no stdin path, so the prompt stays positional
and an oversized one keeps failing loudly rather than silently doing nothing.
Refs #955
* fix(dangerous_commands): cover $HOME expansion and non-sh interpreters (#955)
Two patterns described more than they matched.
`[/~]` reads as "root or home", but home has another spelling. Nothing in this
module expands variables -- shlex.split leaves $HOME as a literal token and the
shell expands it afterwards -- so `rm -rf $HOME` was being checked against a
pattern that could never see it. `\b` after HOME keeps $HOMEDIR and
$HOMEBREW_PREFIX out, which is tested.
`(ba)?sh` reads as "a shell" and matches exactly two of them; `curl ... | zsh`
and `curl ... | python` were not a piped download as far as the denylist was
concerned. The interpreter list now covers the sh family, fish, python, perl,
ruby, node and php, with an optional sudo between the pipe and the interpreter.
The trailing \b also fixes a false positive in the other direction: `(ba)?sh`
with no boundary matched `curl ... | shasum -a 256`, blocking a checksum.
Refs #955
* fix(core): remove dead surface and correct messages that stopped being true (#955)
Each of these told a reader something false.
AgentResultStatus had no production caller and defined TIMEOUT, which
AgentResult.status (a Literal of completed/failed/blocked) will not accept --
so the enum was not just unused but divergent. Removed with its export and its
tests. engine_stats._update_aggregate_stats likewise: no callers, and a
docstring advertising external ones that do not exist.
IsolationLevel.CLOUD still said 'reserved for the future E2B agent adapter
phase' after E2B shipped. Cloud execution is an *engine*, not an isolation
level, so the error now names --engine cloud and --isolation worktree instead of
a phase that already happened.
Builtin requirements() hardcoded ANTHROPIC_API_KEY, so 'cf engines check react'
called the engine unready on an OpenAI or Ollama workspace -- and ready on a
machine that merely had an Anthropic key exported while configured for something
else. It now resolves through the standard provider chain and reuses the
REQUIRED_KEY_ENV map that already existed in llm_resolution; local providers
report nothing to satisfy. check_requirements takes an optional repo_path so the
config.yaml tier applies, and the two CLI call sites pass cwd.
engine_registry built OpenCodeAdapter() with no arguments while every sibling
forwarded **kwargs, silently ignoring the caller's timeout_s and auto_approve.
_truncate_history could return an empty list -- one turn over the budget trimmed
to nothing -- and the caller sends that straight to the provider, where an empty
messages array is an API error. The request failed outright instead of the
history being shortened. It now keeps the last turn and lets the provider's own
limit judge it.
Refs #955
* test: make mocked stderr streams signal EOF (#955)
The bounded stderr drain reads in 65 KiB chunks and stops on a falsy chunk. A
MagicMock configured with `read.return_value = "Fatal error"` returns that same
string on every call, so the loop never reached EOF: the suite hung on the first
test with non-empty mocked stderr and pytest-timeout took the whole session down
with an INTERNALERROR. (Empty-string mocks were unaffected, which is why it
surfaced 197 tests in rather than immediately.)
A fake stream that never signals EOF is a broken fake -- the production loop is
correct for a real pipe -- so the fakes now do what a stream does. Also lifts an
`import os` in builtin.llm_key_requirement to module scope.
Refs #955
* fix(engines): resolve the workspace root, and stop inferring legacy kilo by elimination (#955)
Two defects found by the codex review of this branch.
cf engines list/check passed Path.cwd() straight through. Provider resolution
reads .codeframe/config.yaml in exactly the directory it is handed, so running
either command from repo/src/ missed a workspace configured for OpenAI and
reported ANTHROPIC_API_KEY. Same class of bug as #926, and find_workspace_root
-- added by that issue -- is the fix.
_detect_surface picked legacy by *elimination*: any --help output without the
modern marker counted as evidence for legacy, including output that was not help
text at all. codex's own sandbox demonstrated it -- kilo's log directory was
read-only, so --help printed a Bun EROFS stack trace and exited 1, and the
adapter answered by emitting '--auto --workspace' at a 7.x CLI, where --auto is
the permission bypass #916 established must stay off. The docstring already
promised that a failing --help falls back to modern; the code did not keep it.
Both surfaces are now selected on a marker they actually contain (--workspace
for legacy, 'kilo run' for modern), verified against the captured help of both
real CLIs, so text that is not help matches neither and falls back as
documented.
Refs #955
* fix(dangerous_commands): let sudo carry its own flags; disclose truncated stderr (#955)
Both findings from the PR review (claude-review and the GLM precision review
agreed on the first).
The sudo branch matched only a bare `sudo` directly before the interpreter, so
`curl … | sudo -E bash`, `sudo -i bash` and `sudo -u root bash` all returned
(False, "") while `sudo bash` was caught. Virtually every real sudo-piped
install carries one of those flags, so the download-to-*rooted*-shell case --
the worst one -- was precisely the one getting through, and the added coverage
was narrower than it looked. Verified before fixing: 3 of the 4 spellings
bypassed. `sudo tee` and `sudo -u nobody jq` stay allowed, and are tested.
stdout announced dropped lines; stderr was capped in silence, so a truncated
error message read as the whole story to whoever was debugging a failed run.
It now carries the same kind of marker.
Refs #955
* test(kilocode): skip the installed-CLI smoke tests when --help cannot run (#955)
Second codex review pass, and it is right that the two halves disagreed. This
branch makes an unreadable `kilo --help` a *supported* state -- detection falls
back to modern, asserted directly in test_kilocode_prompt_955 -- but
test_the_installed_cli_matches_one_of_the_two_known_surfaces still read the same
EROFS crash log as evidence of an unknown third surface and failed. Any sandbox
or hardened CI image with kilo installed and an unwritable log dir stayed red on
a case the adapter handles by design.
When the binary cannot produce help, the installed surface is unknowable, so
these two tests have nothing to measure and skip with the reason. Their real job
-- catching the next CLI rewrite -- is unaffected: a kilo whose --help works and
matches neither surface still fails.
Refs #955
* fix(subprocess_adapter): set the stderr-truncation flag on the crossing chunk (#955)
The GLM precision review caught a boundary hole in 86d059f, the commit that
added the marker. Reproduced before fixing.
`retained` counts the whole chunk while only `chunk[:room]` is appended, so the
chunk that crosses the cap takes the retaining branch and leaves the flag alone
-- it was only ever set by a *later* read. When the crossing chunk is the last
one before EOF (total stderr between the cap and one 64 KiB read above it), the
loop exits with the flag still false and the dropped tail reads as a complete
error message. That is precisely the case the marker was added for, so the fix
was defeated in exactly its own boundary window.
The existing test writes 200 KB against a 1000-char cap, so several non-empty
chunks always followed the crossing one and the path never ran. The new test
writes 1500 chars, which arrive in a single read.
Refs #955
0 commit comments