Skip to content

Commit 29805f0

Browse files
yogthosYogthos
andauthored
docs(roadmap): mark Track F shipped (F1-F8, F10, F12-F14, F16-F20) (#92)
All actionable Track F items now landed: - F-CRITICAL (PRs #76, #77): ACP perm asks routed to Deny, find/glob/list_dir hide dotfiles by default - F-HIGH (PRs #78#84): compress aligns cut-point, read streams large files, ACP parallel tool ids, bash pgid cleanup, symlink canonicalize, session schema version, quote-aware bash splitter - F-MEDIUM (PRs #85, #86, #87, #89, #90, #91): interleaved bash output, compress net-savings, Retry-After parsing, plugin docs, relative-path normalize, BOM strip, bounded interject channel - F-SKIP (F9, F11, F15): verified false positives or N/A; rationale documented inline Status legend updated; ordering recommendation replaced with the shipped-status section. Co-authored-by: Yogthos <yogthos@gmail.com>
1 parent 1bed2a5 commit 29805f0

1 file changed

Lines changed: 43 additions & 27 deletions

File tree

ROADMAP.md

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -198,42 +198,55 @@ actual issue.
198198

199199
| Phase | Description | Size | Source |
200200
|---|---|---|---|
201-
| 📋 F1 | **ACP permission asks silently dropped.** `extras/acp/mod.rs:287` builds `(ask_tx, _ask_rx)` and immediately drops `ask_rx`. Tools needing `Ask` confirmation in ACP mode time out at 30s with no notification. Tools requiring permission cannot run from a Zed/editor client. | MEDIUM | opencode permission dispatch via SessionBus |
202-
| 📋 F2 | **Hidden files exposed by `find_files` / `glob` / `list_dir`.** All three set `.hidden(false)` on the ignore walker. Agents reading the filesystem see `.env`, `.git/`, `.DS_Store` and silently pick them up in greps/listings — security + privacy leak. | SMALL | pi + opencode default to hidden-skip; opt-in only |
201+
| F1 | **ACP permission asks silently dropped.** `extras/acp/mod.rs:287` builds `(ask_tx, _ask_rx)` and immediately drops `ask_rx`. Tools needing `Ask` confirmation in ACP mode time out at 30s with no notification. Tools requiring permission cannot run from a Zed/editor client. | MEDIUM | opencode permission dispatch via SessionBus |
202+
| F2 | **Hidden files exposed by `find_files` / `glob` / `list_dir`.** All three set `.hidden(false)` on the ignore walker. Agents reading the filesystem see `.env`, `.git/`, `.DS_Store` and silently pick them up in greps/listings — security + privacy leak. | SMALL | pi + opencode default to hidden-skip; opt-in only |
203203

204204
### F-HIGH — real correctness gaps
205205

206206
| Phase | Description | Size | Source |
207207
|---|---|---|---|
208-
| 📋 F3 | **Compress cut-point can split a tool_use/tool_result pair.** `handle_compress` (`ui/slash.rs:93-101`) does a reverse token-budget scan but doesn't check whether the cut-boundary message has `Interrupted` / pending tool calls. After compress the LLM may see an orphan `tool_use` block. | MEDIUM | opencode `splitTurn` (`compaction.ts:161-184`) respects turn boundaries |
209-
| 📋 F4 | **`read` capped at 10MB with no streaming.** `read.rs:98` does `read_to_string()` whole-file; refuses anything bigger. Large logs / generated files fail. | MEDIUM | opencode `read.ts:119-150` streams + early-terminates; pi `read.ts:215-328` smart-truncates |
210-
| 📋 F5 | **ACP parallel tool calls lose id correlation.** `acp/mod.rs:229-231` uses a single `last_tool_call_id`. Two parallel tool calls + two results → only the second pairs correctly; the first becomes an orphan with an empty id. | SMALL | opencode tracks callID on each ToolPart |
211-
| 📋 F6 | **Bash has no process-group cleanup on timeout/abort.** `bash.rs:76-87` spawns via tokio `.output()` without `setpgid`. Timeout kills only the parent; subprocess tree orphans. | MEDIUM | pi `bash.ts:76-81` uses `detached: true` + `killProcessTree(pid)` |
212-
| 📋 F7 | **Permission `check_path` doesn't canonicalize symlinks.** `resolve_absolute` (`checker.rs:370-376`) joins paths but doesn't follow symlinks. Symlink to `/etc` bypasses `/etc/**` deny. | SMALL | symlink-aware canonicalize at check time |
213-
| 📋 F8 | **No session schema versioning / corruption recovery.** `storage.rs::load_session` deserializes raw JSON; one truncated byte breaks the whole session. No migration path for schema bumps. | MEDIUM | opencode versions message metadata; pi has explicit migration fns |
214-
| 📋 F9 | **Mid-stream decode failure suppresses retry when `had_tool_calls=true`.** `runner.rs:418-427` skips retry if any tool ran. But "tool dispatched, result pending, stream died mid-decode" is the case we DO want to retry — the result event never arrived. | MEDIUM | pi distinguishes "tool dispatched + result received" from "tool dispatched + still pending" |
215-
| 📋 F10 | **Bash fallback splitter (no `semantic-bash` feature) doesn't respect quotes.** `bash.rs:145-163` splits `;`/`&&`/`||` literally. `echo "; rm -rf /"` splits inside the quoted string. Default builds have `semantic-bash` enabled so this only affects `--no-default-features`. | SMALL (mandate semantic-bash) | opencode arity tokenizer handles quotes |
208+
| F3 | **Compress cut-point can split a tool_use/tool_result pair.** `handle_compress` (`ui/slash.rs:93-101`) does a reverse token-budget scan but doesn't check whether the cut-boundary message has `Interrupted` / pending tool calls. After compress the LLM may see an orphan `tool_use` block. | MEDIUM | opencode `splitTurn` (`compaction.ts:161-184`) respects turn boundaries |
209+
| F4 | **`read` capped at 10MB with no streaming.** `read.rs:98` does `read_to_string()` whole-file; refuses anything bigger. Large logs / generated files fail. | MEDIUM | opencode `read.ts:119-150` streams + early-terminates; pi `read.ts:215-328` smart-truncates |
210+
| F5 | **ACP parallel tool calls lose id correlation.** `acp/mod.rs:229-231` uses a single `last_tool_call_id`. Two parallel tool calls + two results → only the second pairs correctly; the first becomes an orphan with an empty id. | SMALL | opencode tracks callID on each ToolPart |
211+
| F6 | **Bash has no process-group cleanup on timeout/abort.** `bash.rs:76-87` spawns via tokio `.output()` without `setpgid`. Timeout kills only the parent; subprocess tree orphans. | MEDIUM | pi `bash.ts:76-81` uses `detached: true` + `killProcessTree(pid)` |
212+
| F7 | **Permission `check_path` doesn't canonicalize symlinks.** `resolve_absolute` (`checker.rs:370-376`) joins paths but doesn't follow symlinks. Symlink to `/etc` bypasses `/etc/**` deny. | SMALL | symlink-aware canonicalize at check time |
213+
| F8 | **No session schema versioning / corruption recovery.** `storage.rs::load_session` deserializes raw JSON; one truncated byte breaks the whole session. No migration path for schema bumps. | MEDIUM | opencode versions message metadata; pi has explicit migration fns |
214+
| 🚫 F9 | **Mid-stream decode failure suppresses retry when `had_tool_calls=true`.** `runner.rs:418-427` skips retry if any tool ran. But "tool dispatched, result pending, stream died mid-decode" is the case we DO want to retry — the result event never arrived. | MEDIUM | pi distinguishes "tool dispatched + result received" from "tool dispatched + still pending" |
215+
| F10 | **Bash fallback splitter (no `semantic-bash` feature) doesn't respect quotes.** `bash.rs:145-163` splits `;`/`&&`/`||` literally. `echo "; rm -rf /"` splits inside the quoted string. Default builds have `semantic-bash` enabled so this only affects `--no-default-features`. | SMALL (mandate semantic-bash) | opencode arity tokenizer handles quotes |
216216

217217
### F-MEDIUM — robustness + edge cases
218218

219219
| Phase | Description | Size | Source |
220220
|---|---|---|---|
221-
| 📋 F11 | **`edit` allows in-call overlapping ranges.** Multi-region replace doesn't check that two `old_text` substrings don't overlap. Order-of-application matters; user is expected to know. | SMALL | pi `edit.ts:31-50` rejects overlaps in schema |
222-
| 📋 F12 | **Bash output is sequential, not interleaved.** `bash.rs:89-102` reads stdout then stderr and concatenates. Time-order lost. | SMALL | pi streams both via `onData()` |
223-
| 📋 F13 | **Compress token math can report negative net savings.** When new summary > replaced messages, `compress_reporting` still reports "saved N tokens" misleadingly. | SMALL | opencode validates `tail+summary` fits budget before issuing call |
224-
| 📋 F14 | **`Retry-After` provider header ignored.** `recovery.rs:38-48` uses fixed exponential backoff regardless of what the API asked. Anthropic + OpenAI send retry-after. | SMALL | opencode reads `retry-after-ms` header |
225-
| 📋 F15 | **`capture_partial_on_abort` doesn't distinguish failed vs interrupted tools.** Tool entries that errored stay `Interrupted` instead of `Failed`. `convert_history` emits different text per state, so the LLM sees "interrupted" when it should see the error. | SMALL | opencode `finalizeInterruptedAssistant` preserves tool state |
226-
| 📋 F16 | **Plugin load order is lexicographic, undocumented.** Multi-file plugin directories sort by filename. Author renames trigger silent order changes. | SMALL | document + recommend numeric prefixes (`00-`); future C3 manifest |
227-
| 📋 F17 | **Plugin context mutability across hooks not documented.** `dispatch_tool_hook` clears slots before the loop, so hook B sees hook A's mutations. Intentional but undocumented. | SMALL | doc comment |
228-
| 📋 F18 | **No relative-path normalization in `is_external_path`.** `checker.rs:334-341` returns `false` for relative paths. In `Accept` mode, `../../etc/passwd` bypasses external_directory rules. | SMALL | normalize via `resolve_absolute` first |
229-
| 📋 F19 | **`read` doesn't strip UTF-8 BOM.** Old Windows files render as invisible-byte-prefix in the LLM context. | SMALL | opencode `Bom.readFile()` detects + strips |
230-
| 📋 F20 | **Unbounded interjection channel.** `mpsc::UnboundedSender<()>` accumulates if user types ahead of the runner. Only the first wakeup matters; channel grows. | SMALL | switch to bounded(64) with `try_send` |
231-
232-
### F-SKIP — verified false positives or design choices
221+
| 🚫 F11 | **`edit` allows in-call overlapping ranges.** Multi-region replace doesn't check that two `old_text` substrings don't overlap. Order-of-application matters; user is expected to know. | SMALL | pi `edit.ts:31-50` rejects overlaps in schema |
222+
| F12 | **Bash output is sequential, not interleaved.** `bash.rs:89-102` reads stdout then stderr and concatenates. Time-order lost. | SMALL | pi streams both via `onData()` |
223+
| F13 | **Compress token math can report negative net savings.** When new summary > replaced messages, `compress_reporting` still reports "saved N tokens" misleadingly. | SMALL | opencode validates `tail+summary` fits budget before issuing call |
224+
| F14 | **`Retry-After` provider header ignored.** `recovery.rs:38-48` uses fixed exponential backoff regardless of what the API asked. Anthropic + OpenAI send retry-after. | SMALL | opencode reads `retry-after-ms` header |
225+
| 🚫 F15 | **`capture_partial_on_abort` doesn't distinguish failed vs interrupted tools.** Tool entries that errored stay `Interrupted` instead of `Failed`. `convert_history` emits different text per state, so the LLM sees "interrupted" when it should see the error. | SMALL | opencode `finalizeInterruptedAssistant` preserves tool state |
226+
| F16 | **Plugin load order is lexicographic, undocumented.** Multi-file plugin directories sort by filename. Author renames trigger silent order changes. | SMALL | document + recommend numeric prefixes (`00-`); future C3 manifest |
227+
| F17 | **Plugin context mutability across hooks not documented.** `dispatch_tool_hook` clears slots before the loop, so hook B sees hook A's mutations. Intentional but undocumented. | SMALL | doc comment |
228+
| F18 | **No relative-path normalization in `is_external_path`.** `checker.rs:334-341` returns `false` for relative paths. In `Accept` mode, `../../etc/passwd` bypasses external_directory rules. | SMALL | normalize via `resolve_absolute` first |
229+
| F19 | **`read` doesn't strip UTF-8 BOM.** Old Windows files render as invisible-byte-prefix in the LLM context. | SMALL | opencode `Bom.readFile()` detects + strips |
230+
| F20 | **Unbounded interjection channel.** `mpsc::UnboundedSender<()>` accumulates if user types ahead of the runner. Only the first wakeup matters; channel grows. | SMALL | switch to bounded(64) with `try_send` |
231+
232+
### F-SKIP — verified false positives, design choices, or N/A
233233

234234
Came back from the surveys but NOT real issues after verification.
235235
Documented here so future audits don't re-raise:
236236

237+
- **🚫 F9 — Mid-stream decode retry**: rig dispatches tools
238+
synchronously inside its stream loop. By the time we observe
239+
`ToolCall`, side effects are applied; retry would re-execute.
240+
`had_tool_calls=true → no retry` is the correct safe behavior.
241+
- **🚫 F11 — Edit overlap detection**: dirge's edit tool is
242+
single-region (one `old_text` / `new_text` per call). The
243+
overlap concern applies to the future multi-file atomic edit
244+
(Track A3), not the current tool.
245+
- **🚫 F15 — Failed vs Interrupted tool state**: rig's
246+
`ToolResult` has no `is_error` field; dirge can't reliably
247+
distinguish "tool ran and returned error text" from "tool ran
248+
successfully" without heuristic string-sniffing. Error text
249+
still reaches the LLM via `Completed{result=error_text}`.
237250
- **System message position violation**: dirge already prepends the
238251
compaction summary as the single System message at index 0;
239252
`convert_history` loops from `first_kept` (typically 1).
@@ -251,12 +264,15 @@ Documented here so future audits don't re-raise:
251264
- **Empty / consecutive assistant messages**: rig filters before
252265
`convert_history` sees them.
253266

254-
### Ordering recommendation
267+
### Status
255268

256-
Ship F1 + F2 first (CRITICAL — one functional break in ACP mode,
257-
one security leak). Then F3–F10 (HIGH) as a single audit-fix PR
258-
series, one phase per PR with TDD per CLAUDE.md global. F11–F20
259-
(MEDIUM) batched into a second wave.
269+
All actionable Track F items shipped:
270+
271+
- F1 / F2 (CRITICAL): PRs #76, #77.
272+
- F3 / F4 / F5 / F6 / F7 / F8 / F10 (HIGH): PRs #78#84.
273+
- F12 / F13 / F14 / F16 / F17 / F18 / F19 / F20 (MEDIUM):
274+
PRs #85, #86, #87, #89, #90, #91.
275+
- F9 / F11 / F15: verified false positives / N/A — see F-SKIP.
260276

261277
## Ideas queue (re-evaluate before scheduling)
262278

0 commit comments

Comments
 (0)