Skip to content

fix(opencode): port 8 isolated bug fixes from upstream opencode - #199

Merged
alltomatos merged 11 commits into
devfrom
claude/practical-mccarthy-nx2ksv
Sep 13, 2026
Merged

alltomatos merged 11 commits into
devfrom
claude/practical-mccarthy-nx2ksv

Conversation

@alltomatos

@alltomatos alltomatos commented Sep 11, 2026 •

Copy link
Copy Markdown
Owner

Issue for this PR

Closes #200

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Compares this fork against upstream anomalyco/opencode and ports 8 small, isolated core/harness bug fixes that were missing here. None of them touch Batuta, Breniac, or the desktop app — all customizations of this fork are untouched.

Note on process: origin/dev and upstream/dev no longer share a common git ancestor (git merge-base returns nothing — upstream appears to have rewritten its history at some point after the fork was created), so a normal git log dev..upstream/dev is not a reliable diff (it lists their entire history, not what's new). Each fix below was instead located via tree/content diffing and upstream's own commit log, then verified byte-for-byte against the real upstream commit before porting.

Fixes included:

  • packages/core/src/aisdk.ts — swallow an SSE reader-cancel rejection on chunk timeout that could surface as an unhandled promise rejection (upstream 69c172e8a7).
  • packages/core/src/database/migration/20260410174513_workspace-name.ts — tolerate a database that predates the workspace.name column instead of failing the migration itself (upstream db86d03b47).
  • packages/core/src/plugin/provider/amazon-bedrock.ts — stop double-prefixing full Bedrock ARNs and non-R1 DeepSeek model ids (upstream ac1758c0e6).
  • packages/core/src/v1/config/provider.ts — allow chunkTimeout: false in provider config, matching the already-supported runtime behavior and headerTimeout's schema (schema portion of upstream 4eb29a64f0; the default-value wiring in packages/opencode/src/provider/provider.ts was intentionally not ported since this fork already has its own diverged fetch-wrapping/timeout code there).
  • packages/opencode/src/session/llm/request.ts — send the x-parent-session-id header for opencode-hosted models too, not just other providers (upstream 611cc73d84).
  • packages/opencode/src/session/processor.ts — log when Anthropic reports it dropped thinking blocks before the model saw the prompt (upstream 3f39a329c3).
  • packages/opencode/src/session/tools.ts — preserve a tool call's original start time when it's re-matched while already running, instead of resetting it (upstream 765ae641d7).
  • packages/opencode/src/tool/apply_patch.ts — omit movePath from patch-change metadata instead of sending it as undefined for non-move changes (upstream f7da00f35e).

Also updated .claude/skills/opencode-fork-expert/references/{fork-map,upstream-sync}.md to document the broken git history between the two remotes and a sandbox-specific network limitation (blocked pkg.pr.new/GitHub tarball fetches) discovered while validating these changes, so future upstream syncs don't rediscover the same dead ends.

How did you verify your code works?

  • Confirmed each ported diff is byte-identical to the real corresponding upstream commit (git show <upstream-hash> -- <file> vs the local file) rather than a speculative rewrite.
  • bun turbo typecheck --filter=@opencode-ai/core --filter=opencode passes clean on both affected packages.
  • packages/app/packages/enterprise typecheck fails in this sandbox due to network-blocked dependencies (ghostty-web, @solidjs/start) unrelated to this change — confirmed via git stash that the same failure exists on a clean dev checkout, so it's a pre-existing environment limitation, not a regression from this PR.
  • A stray, unrelated working-tree edit to packages/app/package.json (removing ghostty-web) was found and discarded rather than committed — it looked like a local workaround for the sandbox's network restriction, not a real upstream fix, and would have broken the terminal feature for real users.

Screenshots / recordings

N/A — no UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code

https://claude.ai/code/session_01AGh7o4NA8jUjwakDzZo12e

wrapSSE used `void reader.cancel(err)` on chunk timeout, which does not
attach a rejection handler — if the underlying stream rejects the
cancel, it surfaces as an unhandled promise rejection. Chain `.catch`
instead so the timeout path can never crash the process.

Ported from upstream commit 69c172e ("fix(provider): handle SSE
reader cancel rejections (anomalyco#44944)"). No behavior change outside the
timeout/cancel path; does not touch Batuta/Breniac/desktop code.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AGh7o4NA8jUjwakDzZo12e
This migration rebuilds `workspace` to add the `name` column, but its
INSERT...SELECT unconditionally read `name` from the old table. On a
database that predates this column, the migration itself would fail
with "no such column: name". Check `PRAGMA table_info` first and fall
back to an empty string when the column isn't there yet.

Ported from upstream commit db86d03 ("fix(core): tolerate missing
workspace names"). Pure migration-safety fix; no schema or behavior
change for databases that already have the column, and unrelated to
Batuta/Breniac/desktop.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AGh7o4NA8jUjwakDzZo12e
resolveModelID() was double-prefixing two cases:
- Full Bedrock ARNs (`arn:...`) got a region prefix appended, which
  Bedrock rejects.
- Any model id containing "deepseek" (not just the R1 models that
  need cross-region inference) got prefixed with the region.

Add an early return for ARN ids and narrow the DeepSeek match to
"deepseek.r1".

Ported from upstream commit ac1758c ("fix: preserve Bedrock
DeepSeek model ids (anomalyco#34441)"). Isolated to Bedrock model id
resolution; no effect on other providers or on Batuta/Breniac/desktop.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AGh7o4NA8jUjwakDzZo12e
`headerTimeout` already accepted `false` to disable its timeout, but
`chunkTimeout`'s schema only accepted a positive integer even though
the runtime (aisdk.ts wrapSSE / provider.ts fetch wrapper) already
treats a non-number chunkTimeout as "no timeout". Users setting
`chunkTimeout: false` in provider config hit a schema validation
error instead of getting the already-supported disable behavior.

Widen the schema to `PositiveInt | Literal(false)`, matching
headerTimeout.

Ported from the schema portion of upstream commit 4eb29a6
("fix(opencode): default chunk timeout to five minutes (anomalyco#46890)").
This fork's fetch-wrapping code in packages/opencode/src/provider/
provider.ts already diverged from upstream's default-to-300000ms
change, so only the schema fix (which matches existing runtime
behavior) was ported — not the default-value wiring, to avoid
touching code this fork customized.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AGh7o4NA8jUjwakDzZo12e
The `x-parent-session-id` header was only attached to the non-opencode
provider branch, so subagent/child sessions running on opencode-hosted
models never told the backend which session spawned them.  Move the
header outside the provider branch so it's always sent when present.

Ported from upstream commit 611cc73 ("fix(opencode): send parent
session header (anomalyco#44752)"). Header-only change; does not affect
Batuta/Breniac/desktop request handling.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AGh7o4NA8jUjwakDzZo12e
Anthropic reports, via providerMetadata.anthropic.inputTransformations,
when it removed thinking blocks before the model saw the prompt. A
non-empty list here means opencode changed conversation history behind
a signed thinking block. Log a warning with the session/message/model
context so this churn is traceable instead of silently ignored.

Ported from upstream commit 3f39a32 ("feat(opencode): tolerate
Anthropic thinking block binding (anomalyco#46653)"). Purely additive logging
on step-finish; no change to request/response handling, and unrelated
to Batuta/Breniac/desktop.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AGh7o4NA8jUjwakDzZo12e
When a tool part transitions back through the "running" resolve path
(e.g. re-emitted deltas for a call already in progress), it reset
`time.start` to `Date.now()` every time, corrupting the reported tool
duration. Keep the original start time when the existing state is
already "running".

Ported from upstream commit 765ae64 ("fix(core): Fix for incorrect
time.start reset in tool call logging (anomalyco#32574) (anomalyco#32596)"). Isolated to
tool-part timing; no change to tool execution or permissions, and
unrelated to Batuta/Breniac/desktop.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AGh7o4NA8jUjwakDzZo12e
The per-change metadata always included `movePath`, even when the
change wasn't a rename, sending `movePath: undefined` explicitly
instead of omitting the key. Spread it in conditionally so the key is
absent for non-move changes.

Ported from upstream commit f7da00f ("fix(opencode): omit empty
apply patch move path (anomalyco#45329)"). Isolated to the patch-change summary
object; no change to patch application logic, and unrelated to
Batuta/Breniac/desktop.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AGh7o4NA8jUjwakDzZo12e
…imits

fork-map.md claimed the upstream remote is always pre-configured and
upstream-sync.md's compare step assumed dev and upstream/dev share a
common ancestor. Neither holds anymore: fresh sandbox clones don't have
the upstream remote, and origin/dev..upstream/dev has no merge-base
(upstream rewrote its history at some point), so commit-range diffing
silently lists their entire history instead of what's actually new.
Documents the tree-diff/byte-diff workaround used to port 8 real fixes
in this session, plus the sandbox proxy blocking pkg.pr.new/GitHub
tarballs that had nothing to do with those fixes but looked like a
regression at first.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AGh7o4NA8jUjwakDzZo12e
@github-actions

Copy link
Copy Markdown

Hey! Your PR title fix(core,opencode): port 8 isolated bug fixes from upstream opencode doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@alltomatos alltomatos changed the title fix(core,opencode): port 8 isolated bug fixes from upstream opencode fix(opencode): port 8 isolated bug fixes from upstream opencode Sep 11, 2026
….spec.ts

The e2e (linux) job on this PR's CI failed with a strict-mode violation:
getByRole('button', { name: 'Close Tab' }) resolves to two elements
whenever a tab is open and showing the not-found fallback, because
Playwright's getByRole name matching is case-insensitive by default and
two different i18n keys produce near-identical accessible names
(common.closeTab -> "Close tab", session.error.notFound.closeTab ->
"Close Tab"). Confirmed this is not caused by this PR's diff (which
doesn't touch packages/app at all): the same failure reproduces
identically on dev HEAD (9ef686e) and on unrelated PRs #190/#191.

A fix already exists as open PR #196 (closes tracking issue #195),
verified there by reproducing the failure and the fix locally. Ported
the same one-line change (adding exact: true) into this PR so CI goes
green here without waiting on #196 to merge first.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AGh7o4NA8jUjwakDzZo12e

Copy link
Copy Markdown
Owner Author

e2e (linux) failed on the first push here with a Close Tab strict-mode-violation in subagent-child-navigation.spec.ts. Not caused by this PR (which doesn't touch packages/app) — the same failure reproduces identically on dev HEAD (9ef686e) and on unrelated PRs #190/#191. Root cause and fix are already tracked in #195 / open PR #196; I ported that same one-line change (exact: true on the locator) here in a2bb7184ed so this PR goes green without waiting on #196 to merge.

Couldn't re-run the Playwright test locally in this sandbox to double-confirm (pinned Chromium 1217 isn't available here, only 1194 — an environment limitation, not a code issue); relying on #196's own local verification (reproduced the failure and the fix 3x) since the change is identical. Will watch this PR's next CI run.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

unit (linux) also failed here, at the "Run HttpApi exerciser gates" step (test:httpapi), with missing=37 routes lacking coverage scenarios (/batuta/*, /agentui/*, /telegram/*, etc.). Not caused by this PR either — same script fails identically on dev HEAD (9ef686e) with the same route list, already tracked in #187, which explicitly documents this as pre-existing and out of scope for unrelated PRs (#161/#162 merged with this same check red). No fix exists yet for #187, and writing the missing HTTP API scenarios is unrelated to and far larger than what this PR does, so I'm not attempting it here — that's #187's job. Standing down on this one.


Generated by Claude Code

…thy-nx2ksv

# Conflicts:
#	packages/app/e2e/regression/subagent-child-navigation.spec.ts
@alltomatos
alltomatos merged commit b5ca120 into dev Sep 13, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: sync fork with upstream opencode bug fixes

2 participants