Days 155–183. If you run cargo with colour enabled, this release is required: watch mode's compiler-error parser was anchored to the start of a line, so a single ANSI escape in front of error[E0308] made it find zero errors — silently, with no warning, for anyone whose terminal or CARGO_TERM_COLOR turns colour on. The same class was fixed for TypeScript. Around that: yoyo learned to sit out a rate limit instead of burning five retries against it, sub-agents survive a dead model instead of dying with it, and a project-config trust boundary means a repository you just cloned can no longer silently grant itself MCP servers, auto-approved shell commands, or hooks. Plus yoagent 0.16.6 → 0.18.1, a pile of correctness fixes in commit-message generation and syntax highlighting, and four commands that worked in the REPL and silently became billed LLM prompts at the shell.
Added
--wait-for-reset— opt-in permission to wait out a provider rate limit (up to 6h) instead of giving up, reading the provider's own reset time rather than guessing; also settable aswait_for_reset = truein your config. Default off, because a process that can silently sleep for hours is not a safe default (Day 178)- Rate-limit-aware retry — when a provider reports when it will accept work again, yoyo honours that number instead of five capped backoffs against the same refusal, and says when the door reopens instead of failing silently (Day 177)
- Sub-agent model fallback — if the session's model is unavailable, a dispatched
sub_agentretries once against your configured fallback model instead of dying. Announces the switch; never fires on auth failures, rate limits, or a deliberate refusal (Day 180) - Sub-agent failure diagnostics — the parent now receives the error's class, HTTP status and model configuration instead of an opaque summary string, so it can tell "model unavailable" from "the delegated work failed" (Day 180)
- Partial sub-agent results are labelled — a sub-agent stopped by its turn budget returns
Okwith a truncated answer; that result is now annotated as partial rather than reading as finished work (Day 182) - Project-config trust boundary — a project-local
.yoyo.tomlcan no longer silently start MCP servers, grantpermissions.allow, or run shell hooks, and.yoyo/goal_verify.mdno longer executes on/goal check. yoyo asks once per folder, remembers your answer with--trust-project-always, and names every grant before you answer. A non-interactive run always takes the safe answer (Days 166, 167, 175, 178) - Wildcards in
[directories]—deny = ["*/secrets"]andallow = ["src/*"]now match; previously a wildcard entry matched nothing at all, so adenypattern silently fenced nothing and anallowpattern locked you out of every file (Day 178) - Failed MCP/OpenAPI servers are reported to the model — previously a server that failed to connect left the model with a shorter tool list and no error, so it concluded the capability did not exist and silently worked around it (Day 181)
- Runaway-loop notice — when yoyo stops a run for repeating an identical tool call, it now says so and states that this was deliberate, rather than stopping with no stated reason (Day 181)
- Malformed tool calls get one resample — a turn whose tool arguments never assembled is retried once instead of ending the run, bounded and announced (Day 183)
- CLI doors for
/astand/def—yoyo ast '$X.unwrap()' --in src/andyoyo def <symbol>now reach the real handlers; they used to fall through and run as billed LLM prompts (Day 169) /testforwards its arguments —yoyo test --liband/test --libreach your test runner instead of silently running the whole suite (Day 166)- A mistyped subcommand is caught, not billed —
yoyo statsunow suggests the real command and exits, instead of being sent to the model as a prompt with write-capable tools attached (Day 165) /config shownames the config files it skipped — first match wins and config files are not merged, so a shadowed~/.yoyo.tomlis no longer invisible in the one command you would check (Day 179)
Improved
- yoagent 0.16.6 → 0.18.1 — two minor versions of the agent core, including loop detection and context-compaction events yoyo now handles explicitly (Day 181)
- Syntax highlighting carries state across lines — block comments, multi-line strings, raw strings (
r#"…"#) and JS/Go backtick literals no longer mis-colour every line after the first (Days 171, 173) - Commit messages generated by
/commitdescribe the diff — the type is chosen by which files carry the most changed lines rather than by whichever file matched first, whole-file deletions are counted at all, and commits over three files name the largest change instead of collapsing to "update code" (Days 174, 178) - Hook stderr is bounded before it enters the conversation — a shell hook printing megabytes could previously overflow the context and wedge the session; now capped head-and-tail with the cut marked in band (Day 180)
- Long diff lines are bounded — a diff touching a base64 blob, a minified bundle or a lockfile hash is one line, so it passed the existing line-count cap and rendered whole into your terminal (Day 179)
Fixed
- Coloured compiler output parsed to zero errors — watch mode's Rust error parser anchored on
error[at column 0, so any ANSI escape made it find nothing; auto-fix then ran with no errors to fix (#859, Day 182) - TypeScript errors missed under
--prettyor colour — the same anchoring bug, plustsc's pretty format (file:line:col - error TSxxxx) never matched any pattern (#861, Day 182) - Rate limits classified as fatal client errors — status codes were matched as bare substrings, so
retry after Some(14454000)mscontained400and read as a terminal error. This disabled backoff and--wait-for-resetfor the exact shape they exist for (#852, Day 181) - A token-count overflow diagnosed as exhausted credits —
prompt is too long: 402134 tokenscontains402, so yoyo confidently told you retrying would not help when the fix was/compact(Day 174) /readand/planmode did not blockgit commit—gitwas in neither the write-verb list nor the destructive-pattern list, so the single most consequential write in your repository passed straight through a mode whose whole promise is mechanical enforcement (#838, Day 179)--allowed-toolsand--output-formatswallowed the next flag —yoyo --allowed-tools --model gpt-5consumed--modelas a value, so it never fired and no warning was possible (#862, Day 182)deny = ["~/.ssh"]never matched anything — a leading~was treated as a relative path and joined onto the working directory, so the documented way to fence off your SSH keys resolved to a directory that cannot exist (Day 177)- A narrow-looking
permissions.allowpattern auto-approved arbitrary commands —git * mainalso matchedgit -c core.sshCommand=<anything> push main, for the whole session (Day 178) - Auto-context ignored your directory restrictions — a file yoyo refused to read when the model asked for it was silently pasted into the same API request by automatic context injection (#817, Day 175)
- Non-ASCII filenames came back as escaped byte strings — every git-derived file list (project context,
/tree,/rename, risk scoring) received"src/n\303\244me.rs"instead of a usable path (#863, Days 182, 183) /renamereported no changes — a multi-file rename wrote files and recorded nothing, so the per-turn summary,--output-format json, and the post-prompt watch gate all read it as "nothing happened" (#778/#783/#786, Day 170)/extractcould cut a symbol at the wrong line — the brace scanner counted braces inside strings, char literals and comments, soprintln!("}")ended a block early and left the code in neither file (#770/#771, Days 169, 171)- Rust lifetimes broke syntax highlighting —
&'a stropened a string literal that never closed, so the rest of every such line rendered in string colour (#759, Day 169) /updatecould not succeed on Windows — a hardcoded/tmppath, a shared extract directory that could read a half-extracted tree from a crashed run, andunzip(which Windows does not ship) invoked for the Windows archive (#756, Day 169)/bg listshowed elapsed-since-start as runtime — a job that took 0.2 seconds read✓ done 1h3man hour later (#736, Day 165)/revisit adddestroyed the candidate list — an unreadable or truncated file read as empty, and the next add overwrote every surviving entry under a green success line; it now refuses. Entries also store the real issue title instead of a permanent(issue #N)placeholder (#740, #741, Days 165, 170)/goal checkran the project's verify command before refusing — the refusal printed after the side effect it appeared to be declining (#754, Day 166)/remember [category:typo]silently stored the malformed prefix inside the note — under a category you did not ask for, with a success line; it now refuses and lists the real categories (#769, Day 168)/ast '$X.unwrap()' src/'searched for the path as part of the pattern — a bare path argument is now refused with the working form named, instead of silently reporting no matches (#767, Day 168)- A failed MCP connect reported a wrong server count — the rebuilt agent carries zero external connections, so with three servers and one failure you lost all three and were told you had two. A transient pre-flight failure also disabled the tool-name collision guard entirely, killing the session on turn one (#841, #842, Days 180, 181)
- The setup wizard silently demoted or shadowed your existing config — saving to a project file demotes a working
~/.yoyo.toml; saving to the user file is shadowed by any./.yoyo.toml. Both now say so (#816, Day 175) - The post-prompt watch cycle ran on turns that changed nothing — after any earlier edit in the session, a zero-change
/extendedturn ran the full lint→fix→test cycle and told the model "your changes caused failures" about a pre-existing failure (#818, Day 175) - The audit log double-counted every tool call — and recorded failed calls as successes with a fabricated zero duration (#751, Day 166)