anomalyco / anomalyco/opencode
OpenCode 1.18 field notes: loops, origins, agents, sessions, plan mode, and privacy routing
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
OpenCode 1.18 field notes: loops, origins, agents, sessions, plan mode, and privacy routing
These are things that bit real users on OpenCode 1.18.x (verified through 1.18.31, Windows + local and cloud models). Written so you can use them without knowing our setup. The Plan-mode section is a full stock permission matrix, not a one-liner — several built-ins look locked down and are not.
This is one post, not a pile of new issues. Two earlier drafts (the doom_loop gap and silent project_id=global) are finished here instead of being filed as separate writeups. Where an upstream issue already exists, comment there rather than opening a duplicate.
Not claiming: native doom_loop is fixed. #25254 was auto-closed for inactivity. The same processor.ts logic is still what 1.18.31 runs.
1. doom_loop is blind across turns (and can miss even inside one message)
What's broken
Docs say doom_loop fires when the same tool repeats 3 times with identical input, then asks (default) or denys.
The detector in packages/opencode/src/session/processor.ts does not do that in the way people expect:
const parts = /* parts of the CURRENT assistant message only */
const recentParts = parts.slice(-DOOM_LOOP_THRESHOLD) // 3
if (
recentParts.length !== 3 ||
!recentParts.every(
(part) =>
part.type === "tool" &&
part.tool === value.name &&
part.state.status !== "pending" &&
JSON.stringify(part.state.input) === JSON.stringify(input),
)
) {
return // no doom_loop
}
Two independent misses, both described in #25254:
- Current-message only. Repeats across assistant turns never accumulate. One failed
editper message, five messages in a row → never 3 matching parts in one message. - Slice, then
every(). If the last three parts includestep-start, text, or reasoning,every()is false even when the message also contains three identical tool calls.
A third gap the docs do not mention:
- Exact JSON only.
editfail →editfail (same payload) →readnever hits 3 identical calls. Slightly differentoldStringon the same file never hits either. That is a common real retry loop.
steps has no default. If you never set agent.<name>.steps, the agent iterates until the model stops or you interrupt. Official agents docs: unset steps → no iteration limit.
#25254 is closed by the 60-day stale bot. Fix PRs (#25255, #32089) did not land in 1.18.31. Treat the issue as still live.
Related: #32187 (granularity), #23531 (configurable threshold).
Repro
A. Production-shaped miss (cleanest)
- OpenCode 1.18.x. Do not set
steps. Leavepermission.doom_loopat"ask". - Point the agent at a real file. Have it
editwith anoldStringthat is not in the file. - Typical live sequence: failed
edit→ identical failededit→read(or a thirdeditwith a differentoldString). - No
doom_looppermission prompt. The run continues.
B. Identical batch can still miss
opencode run --agent build --format json --auto "Call edit five times with the exact same filePath/oldString/newString. oldString must not exist in the file."
On 1.18.31, five identical failed edits in one assistant message produced no doom_loop permission event in the JSON stream. A non-tool part in slice(-3) is enough to skip the check. --auto auto-approves asks, so the stream absence is the evidence — not a claim about a hidden TUI dialog.
C. No default step cap
opencode debug config / resolved agent: if you never set steps, the field is absent. Unset → unbounded iteration.
What actually stops the loop today
Native doom_loop is not something you can patch from config. Two user-space backstops work.
1. Session-wide plugin (put a .js / .ts in ~/.config/opencode/plugins/ — auto-loaded). --pure and --auto silently undo this. See section 11 before you copy a headless recipe.
- Count identical
edit/write/patchcalls across the session, not one message. Throw on the 3rd. - Separately count failed edits on the same file even when
newStringchanges. Throw on the 3rd. - Ignore
readso rereading a file is not a false positive.
tool.execute.before throw aborts the call. tool.execute.after throw is not a reliable session stop — arm a flag in after and throw on the next before.
MCP tools (including Playwright) do not get these hooks. A browser loop is not caught by this plugin.
Minimal shape (no logging of arguments):
const LIMIT = 3
const EDIT = { edit: true, write: true, apply_patch: true, patch: true }
const sessions = new Map()
export const FailedEditLoopGuard = async () => ({
"tool.execute.before": async (input, output) => {
const st = sessions.get(input.sessionID) || { sig: "", n: 0, armed: false }
sessions.set(input.sessionID, st)
if (st.armed) throw new Error("LOOP_GUARD: too many failed edits on the same file")
if (!EDIT[input.tool]) return
const sig = JSON.stringify(output.args || {})
st.n = sig === st.sig ? st.n + 1 : 1
st.sig = sig
if (st.n >= LIMIT) throw new Error("LOOP_GUARD: identical edit repeated")
},
"tool.execute.after": async (input, output) => {
if (!EDIT[input.tool]) return
const text = String(output?.output || output?.error || "").toLowerCase()
const failed = Boolean(output?.error) || text.includes("not found") || text.includes("old_string")
const st = sessions.get(input.sessionID)
if (!st) return
if (failed) {
st.fails = (st.fails || 0) + 1
if (st.fails >= LIMIT) st.armed = true
} else {
st.fails = 0
}
},
})
export default FailedEditLoopGuard
Verified on 1.18.31: 3rd identical failed edit returned LOOP_GUARD and the file was unchanged. Native doom_loop had not stopped the first two.
2. Hard steps cap (this is the unbounded-run backstop, even if the plugin misses):
{
"permission": { "doom_loop": "ask" },
"agent": {
"build": { "steps": 50 },
"plan": { "steps": 50 }
}
}
A plugin config hook can fill steps: 50 on any agent that omitted it. Explicit steps must be left alone.
Verified on 1.18.31 with steps: 3: step 1 tool, step 2 tool, step 3 text-only CRITICAL - MAXIMUM STEPS REACHED. Remaining tool work did not run.
doom_loop: ask is already the documented default. Setting it only makes the config explicit. It does not widen the matcher.
Suggested upstream fix (comment on #25254 or reopen)
- Filter matching tool+input first, then count; do not
slice(-3)a mixed part list. - Count since the last user turn, not only
ctx.assistantMessage.id. - Default
steps(or a documented global cap) so unset config cannot run forever. - Optional second rule: same tool + same path + N consecutive failures, intervening
readof that path does not reset.
2. Playwright MCP origin-lock vs real websites
What's broken
@playwright/mcp defaults to allow all origins. An --allowed-origins list is a deny-by-default allowlist. If you set it to one client site plus localhost (a common “be careful” instinct), the agent cannot open any other public URL. That is a hard blocker for research, QA, or visual review of arbitrary sites.
--allowed-origins is not a documented place for https://*. Supported forms are a full origin (https://example.com:8080) or a wildcard port (http://localhost:*). You cannot express “any https business site” as an allowlist without listing every host.
What to do instead
Drop --allowed-origins. Use --blocked-origins (evaluated first) for the origins you actually must not hit, and leave public https allowed:
{
"mcp": {
"playwright": {
"type": "local",
"command": [
"npx",
"-y",
"@playwright/mcp@latest",
"--isolated",
"--blocked-origins",
"file://;http://169.254.169.254;https://169.254.169.254;http://metadata.google.internal;http://metadata.google.internal.;http://100.100.100.200;https://100.100.100.200"
]
}
}
}
Why those blocks (SSRF-relevant, not theater):
| Origin | Why |
|---|---|
file:// |
Browser navigating to local files. MCP also blocks file:// unless --allow-unrestricted-file-access; keep it on the list anyway. |
169.254.169.254 |
Cloud instance-metadata (AWS / Azure / GCP). Classic SSRF target. |
metadata.google.internal |
GCP metadata DNS name (trailing-dot variant too). |
100.100.100.200 |
Alibaba metadata. |
Leave localhost / 127.0.0.1 off the blocklist if you use Playwright to review a local preview server.
Microsoft’s own docs: origin filtering is not a security boundary and does not follow redirects. This is use-case scoping, not a sandbox. Pair it with OS egress controls if you need a real boundary.
Verified with the same flag string: https://example.com and another public site allowed; file:// and http://169.254.169.254/... blocked.
3. Desktop: custom agents collapse to built-in general (#29616)
What's broken
Custom agents in ~/.config/opencode/agents/*.md (or opencode.json) often work with:
opencode --agent your-agent
On Desktop, when the parent calls the task tool, subagent_type still only offers built-ins (explore, general, …). The model then roleplays: subagent_type: "general" plus a prompt like “act as <your-agent>”. You do not get that agent’s model, permission set, or prompt.
This is not “the parent model is too small.” Same miss with a local parent and with a high-capacity cloud parent. The enum never included the custom name.
Still present as of Desktop 1.18.21 (and the 1.18.x line). See also #31025, #26516.
Workaround
- Prefer a skill (
~/.config/opencode/skills/<name>/SKILL.md) when you need Desktop to follow a named workflow inside the current session. - Or start the session from CLI:
opencode --agent <name>/opencode run --agent <name>. - Do not trust a Desktop
taskcall that says it spawned your custom agent unless the tool payload’ssubagent_typeis actually that name.
4. Desktop Home list: no delete (#40786)
The Home session list has no delete/archive control (SHOW_HOME_SESSION_ARCHIVE is hardcoded false; rows have no context menu). Related: #38820, #33129, #41265.
Workaround — CLI, not sqlite:
opencode session list
opencode session delete ses_...
You can delete from an open session’s header ⋯ menu in some builds. That still requires opening the session first. The Home list itself does not clean up.
5. Built-in permission matrix (Plan is not locked down)
Marketing and the agents docs disagree with each other, and both disagree with 1.18.31 source (packages/opencode/src/agent/agent.ts). The table below is the stock merge before your opencode.json overlays. Last matching rule wins; user permission and agent.<name>.permission are applied on top.
Shared base (every built-in starts here)
| Action | Stock |
|---|---|
* (anything not listed) |
allow |
read |
allow, except *.env / *.env.* → ask; *.env.example → allow |
external_directory |
ask, except OpenCode tmp / truncate / skill dirs → allow |
doom_loop |
ask |
question |
deny |
plan_enter |
deny |
plan_exit |
deny |
There is no stock deny on bash / edit / task / webfetch / websearch / skill. Those are allow unless a named agent overrides them.
What each shipped agent actually changes
Legend: A = allow, D = deny, Q = ask, — = inherits the base (so allow, unless the row is already Q/D). edit covers edit / write / patch.
| Action | build |
plan |
general |
explore |
compaction / title / summary |
|---|---|---|---|---|---|
| Mode | primary | primary | subagent | subagent | hidden primary |
read / glob / grep / list |
— (A) | — (A) | — (A) | A (re-allowed after *: deny) |
D (*: deny) |
edit (workspace files) |
— (A) | D, except .opencode/plans/*.md and the managed plan dir |
— (A) | D (caught by *: deny) |
D |
bash |
— (A) | — (A) | — (A) | A | D |
webfetch / websearch |
— (A) | — (A) | — (A) | A | D |
task / subagents |
— (A) | D for general only; other names inherit A |
— (A) | D (*: deny) |
D |
todowrite |
— (A) | — (A) | D | D | D |
question |
A | A | D (base) | D | D |
plan_enter |
A | D (base) | D | D | D |
plan_exit |
D (base) | A | D | D | D |
external_directory |
Q (base) | Q, plus managed plan dir A | Q | Q (readonly-style whitelist) | D |
doom_loop |
Q | Q | Q | Q | Q (irrelevant; no tools) |
steps |
unset (unlimited) | unset | unset | unset | unset |
compaction / title / summary are hidden primaries with *: deny. They are not modes you Tab into. Compaction still rewrites session history; it does not need tools to do that.
The two “looks locked, isn’t” rows
Plan. The point of the mode is “don’t write the repo.” That part is real. What people then assume — no shell, no network, no subagents, no search — is not. Stock plan still allows bash, webfetch, websearch, and read/grep/glob. The only extra task deny is task.general. Older agents-doc wording that Plan sets bash to ask is not what 1.18.31 ships. If you need a read-only planner, say so:
{
"agent": {
"plan": {
"permission": {
"edit": "deny",
"bash": "deny",
"webfetch": "deny",
"websearch": "deny",
"task": "deny"
}
}
}
}
A planner that “won’t even open files” is the opposite mistake: read was never denied. Check for a custom overlay that denied it.
Explore. Docs call it read-only / “cannot modify files.” File writes are denied. bash is explicitly allowed. That is a full execute path on a subagent people spawn for “just look around.” Deny bash on explore if that is not what you meant.
General is not a sandbox either. It has the same *: allow as build except todowrite: deny. When Desktop’s task tool collapses a custom agent into general (section 3), you get this matrix — edits and shell included.
Hidden agents aside, nothing in the stock set is an air gap. *: allow is the default. Deny is opt-in.
6. Same model name, different provider: Zen/Go bypasses OpenRouter privacy
OpenCode can list GLM 5.3 Flash (and many others) under more than one provider group:
| You pick | Request goes to | OpenRouter ZDR / data_collection |
|---|---|---|
opencode/... (Zen) |
OpenCode Zen | No |
opencode-go/... (Go) |
OpenCode Go (https://opencode.ai/zen/go/v1/...) |
No |
openrouter/... |
OpenRouter | Yes, if you set them |
Account-level OpenRouter privacy (ZDR, data_collection: deny, provider preferences) applies only to requests OpenRouter actually routes. Selecting the model from the Zen or Go catalog is a different HTTP path. The label in the picker can look like the same model.
To have ZDR apply:
- Select the model from the OpenRouter provider group (
openrouter/<org>/<model>). - Enforce ZDR on that path (
provider.zdr: trueand/or account privacy +data_collection: deny).
Zen/Go have their own terms and exceptions (some Go SKUs are explicitly not ZDR). Do not assume “I turned on OpenRouter ZDR, therefore every GLM call in OpenCode is ZDR.”
A provider.opencode.blacklist / provider.opencode-go.blacklist in opencode.json only hides those provider IDs. It does not move traffic onto OpenRouter.
7. Non-git folders silently share project_id=global (#18890)
Finished from the earlier draft; still the same mechanism.
If the working directory has no .git, Project.fromDirectory() assigns a hardcoded project_id = global (worktree often /). Sessions from unrelated non-git folders share one project bucket. There is no warning in CLI or Desktop.
That mixes session lists, --continue, and anything keyed on project_id (including which project opencode.json / default_agent you think you are using).
Related: #15719 (recent projects show /), #16812 / #21230 (git init does not reliably re-home old global sessions). TUI list filtering PRs reduce some leakage; they do not give each non-git directory its own id.
Repro
mkdir /tmp/oc-site-a /tmp/oc-site-b
echo a > /tmp/oc-site-a/index.html
echo b > /tmp/oc-site-b/index.html
opencode run --dir /tmp/oc-site-a "hello"
opencode run --dir /tmp/oc-site-b "hello"
Inspect the session table: both rows project_id = 'global'. No banner.
Optional: git init && git commit --allow-empty -m init in site-a, then another session in that folder or a subdir. New rows may still be global until a real project row exists and migrateFromGlobal() actually moves them.
Unrelated git repos on the same machine (id = root commit hash) are not in that bucket. Only unresolved / non-git ones.
Workaround (does not fix the silence)
git init+ first commit in the folder you want treated as a project.- Add a project-level
opencode.jsonif you need a pin (default_agent, MCP, …). - Open /
--dirthat git root, not a random parent and not Desktop’s empty default folder.
This does not warn that you are already in global, does not always migrate old rows, and does not give each non-git directory its own id.
Suggested upstream
- Derive a non-git project id from the resolved directory path (#18890).
- Warn once when a session is created under the fallback.
- Make
migrateFromGlobal()move subdirectory sessions aftergit init(#21230).
8. Compaction can wipe an output contract mid-chain
compaction.auto defaults to true. There is no 75% knob in the 1.18.x schema; the trigger is a reserved-token buffer, not a percentage you set. compaction.prune defaults false.
On a long tool chain the hidden compaction agent rewrites history, then the session continues with a synthetic compaction_continue. The model still has “you were doing something,” but a strict output contract (fixed headings, four required lines, “end with exactly this schema”) is often gone. The work already done is real; the required closer is not.
Verified: a 30-read chain completed every named read, then compacted, then answered with analysis / “what next?” and never the required four-line footer. A sibling run that stayed under the buffer (larger context, no compaction_continue) kept the closer in play — and could still fail the format for model reasons. Treat those as different failures.
This compounds with section 1. A long retry loop fills the window, compaction fires, the “stop and report X” instruction disappears, and doom_loop still has not.
What to do:
- Do not score a compacted run as “the model ignored the format.” Check the event stream for
agent=compaction/compaction_continuefirst. - For contract-critical harnesses:
"compaction": { "auto": false }on that run, or give the model enough context that the buffer never trips. - After a live compact, re-state the closer. Compaction is a history rewrite, not a reminder.
9. A new session from a child inherits the active agent, not default_agent
default_agent is used when a session is created with no agent already selected. It does not rewrite an agent stored on a session, and it is not re-read when you spawn a new session from inside an existing one.
If you are in a child (or you Tabbed the parent to build / a custom primary) and you start a new session from there, the new session keeps whatever agent was active. Project default_agent (for example plan, or a deny-all lean primary) is not consulted. Easy to file as a routing bug. It is session-create inheriting the current mode.
Reported as unexpected: #29594 (session_new from a switched parent). Related: #27370 (resumed sessions also keep their stored agent).
What to do: set the agent on the new session (--agent, Tab, Desktop picker) instead of assuming the project pin. default_agent still applies to a cold start from Home / opencode run with no --agent.
A subagent cannot be the session agent. --agent on a mode: subagent name falls back (usually to build / the first visible primary). That is a separate, also easy-to-miss, rule.
10. Other quietly consequential defaults
Small, verified, and harmless-looking until they combine with something above.
permission.mcp is not “all MCP tools.” It is a permission for a tool literally named mcp. Live MCP tools are servername_toolname (Playwright → playwright_browser_*). "permission": { "mcp": "deny" } on a lean agent does nothing to them; they inherit *: allow. Deny the actual prefix.
Desktop “hide model” ≠ opencode.json blacklist. Manage Models writes a Desktop visibility store. CLI opencode run --model … still sees every ID. provider.<id>.blacklist is the CLI/config gate. Two stores.
Windows: opencode.cmd plus < in the prompt silently truncates. cmd.exe treats <...> as redirection. A multiline Start-Process opencode … (PATH → .cmd) can deliver only the first sentence. The model then looks like it “ignored the file list.” Call opencode.exe with a prompt file; do not put < through the cmd shim.
--pure / --auto: not a one-liner. Section 11.
default_agent must be a visible primary. A subagent or hidden: true name falls back to build (with a warning). A deny-all custom primary as default_agent is valid — and then every consumer that does not pass --agent (including other apps that shell out to opencode) gets no tools.
explore allows bash. Covered in the matrix. Repeated here because “read-only subagent” is the phrase that causes the spawn.
Zen/Go blacklist ≠ OpenRouter hide. provider.opencode.blacklist does not remove openrouter/<same-name>. Privacy routing is still section 6.
opencode debug config expands env API keys into plaintext. Do not paste that dump. Prefer opencode debug agent <name> and quote only permission / steps / model-id fields.
11. --pure and --auto silently disable the loop-guard you just installed
These are official opencode run flags. Neither errors. Neither names what it turned off.
| Flag | What the help text says | What it actually does to section 1 |
|---|---|---|
--pure |
“run without external plugins” | Does not load ~/.config/opencode/plugins/ (or project .opencode/plugins/). The session-wide failed-edit guard from section 1 is not there. No “skipped N plugins” line. |
--auto |
“auto-approve permissions that are not explicitly denied (dangerous!)” | If native doom_loop ever asks, the ask is approved and the loop continues. The JSON stream may show no useful permission event. |
Practical consequence: if you installed the plugin workaround in section 1, then ran a headless recipe that includes either flag, you are back to the original failure mode. No warning that the guard is gone. --pure removes the plugin. --auto auto-approves the native ask the plugin was meant to replace. Together they remove both user-space stops that still depend on plugins or ask.
What still works under those flags: an explicit agent.<name>.steps cap (section 1, backstop 2). That is config, not a plugin, and it is not a permission prompt. If steps is unset — the stock default — --pure and/or --auto leave you with nothing.
--pure is useful for “reproducible, no third-party code.” --auto is useful for CI. Both are legitimate. The miss is the silence when they override an active safety plugin or doom_loop: ask.
Do not copy opencode run --pure --auto … from a gist onto a machine whose only loop protection is that plugin.
12. Framework perspective (ideas, not a charge sheet)
The pattern in this post is the same one: a safety control exists, a second switch turns it off, and the user is not told. Maintainers may already have weighed these and rejected them for isolation, CI, or support reasons that are not obvious from outside. Offered as design questions, not as “you should have.”
-
Warn when a flag overrides an active guard. If
--pureskips loaded plugins, print one line: plugins not loaded (and a count). If--autowill auto-approvedoom_loop, print thatdoom_loopasks will not stop the run. Same idea for Plan/Explore labels that do not match the permission matrix: a one-time doc or TUI hint beats discoveringbashis allowed after a spawn. -
Let a plugin declare itself non-skippable. Something like
required: true/safety: trueon a plugin so--purecannot drop a loop guard without an extra explicit--pure-unsafe(or a prompt).--purecan stay the default isolation tool; the distinction is “no convenience plugins” vs. “no safety hooks either.” Today that distinction does not exist. -
Default a harness-level cap, and keep
askout of--autofor loop detection. Unsetstepsplus current-messagedoom_loopplus--autois three silent “continue”s stacked. A conservative defaultsteps(overridable to unlimited) and a carve-out so--autodoes not approvedoom_loopwould close the class without removing either flag. Compaction wiping a closer (section 8) is the same class: a system rewrite with no “your required format was dropped” receipt.
None of this replaces fixing #25254 in processor.ts. It is about making the overrides visible so a user-space workaround does not vanish.
Quick map
| Symptom | Likely cause | What to do now | Upstream |
|---|---|---|---|
| Agent retries the same failed edit forever | doom_loop is current-message + exact JSON + slice-before-filter; steps unset |
Plugin + explicit steps |
Reopen / comment #25254 |
Plugin / doom_loop ask did nothing in a headless run |
--pure skipped plugins and/or --auto approved the ask |
Drop those flags, or keep steps set; see §11 |
Flags are silent overrides |
| Playwright can’t open any site except one allowlisted host | --allowed-origins is deny-by-default |
Remove allowlist; --blocked-origins for file:// + cloud metadata |
Config, not an OpenCode bug |
Desktop “spawned my custom agent” but used general |
task enum is built-ins |
Skills, or opencode --agent |
#29616 |
| Can’t delete sessions from Desktop Home | No list affordance | opencode session delete <id> |
#40786 |
| Plan / Explore “must be locked down” | Stock *: allow; Plan denies writes only; Explore allows bash |
Explicit denials; do not trust the label | Source agent.ts 1.18.31, not the older agents-doc ask list |
| Long run forgot the required footer | Auto-compaction rewrote history (compaction_continue) |
Score only after checking compact events; compaction.auto: false for harnesses |
Config default, not a model bug |
New session ignored default_agent |
session_new inherited the active agent |
Set --agent / picker on the new session |
#29594 |
| Lean agent still called Playwright / other MCP | permission.mcp ≠ playwright_browser_* |
Deny the real tool prefix | Docs vs live tool names |
Windows prompt “lost” <...> |
opencode.cmd redirection |
opencode.exe + prompt file |
cmd.exe, not the model |
| OpenRouter ZDR didn’t apply to GLM | You picked Zen/Go | Pick openrouter/... |
Provider routing, not a toggle bug |
| Sessions from two non-git folders mixed | Shared project_id=global |
git init + open the repo root |
#18890 |
Version / how this was checked
- OpenCode 1.18.31 (CLI). Desktop custom-agent miss last confirmed on 1.18.21.
- OS: Windows. Local OpenAI-compatible endpoint and cloud providers. Behavior is harness-side, not model-specific.
doom_loopsource re-read from currentprocessor.ts. Plugin +steps: 3proven with liveopencode run --format json(not config inspection alone). Playwright flags proven by driving@playwright/mcpnavigate allow/block.- Permission matrix is the 1.18.31
agent.tsmerge, not a custom overlay and not the older “Plan sets bash to ask” doc line. Compaction-vs-contract: live 30-read chain thencompaction_continue, closer gone; larger-context sibling had no compact event. - No personal paths, account ids, or client names in this post.
opencode debug configcan expand env API keys into plaintext — do not paste that dump into GitHub.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
This post combines several independent reports, including packages/opencode/src/session/processor.ts for doom_loop behavior and packages/opencode/src/agent/agent.ts for permissions, plus Desktop and Playwright concerns. Start by selecting one behavior and its referenced upstream issue, then reproduce it with the commands or versions given. Done is not defined for the post as a whole because it contains multiple proposed fixes and workarounds.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- playwright, typescript
- Domain
- desktop, devtools, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100