Created
February 18, 2026 17:15
-
-
Save bkataru/27caa1aeb746efb06f83127cfcc586b0 to your computer and use it in GitHub Desktop.
opencode titlecase crash fix: TypeError when task() called without subagent_type in TypeScript project directories
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # opencode `titlecase` Crash Fix — Binary Patch | |
| ## Problem | |
| When using [opencode](https://github.com/anomalyco/opencode) (v1.2.6) with the [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) plugin in **TypeScript project directories**, the process crashes with: | |
| ``` | |
| TypeError: undefined is not an object (evaluating 'str3.replace') | |
| at titlecase (src/util/locale.ts:3:12) | |
| at task (src/cli/cmd/run.ts:170:24) | |
| at loop (src/cli/cmd/run.ts:472:15) | |
| ``` | |
| ### Root Cause | |
| In `src/cli/cmd/run.ts`, line 170: | |
| ```typescript | |
| Locale.titlecase(info.input.subagent_type) | |
| ``` | |
| This is called when the model invokes the `task()` tool. When the model omits `subagent_type` (which is optional in the schema), `info.input.subagent_type` is `undefined`. The `titlecase` function in `src/util/locale.ts` has no null guard: | |
| ```typescript | |
| // src/util/locale.ts | |
| function titlecase(str) { | |
| return str.replace(/\b\w/g, (c) => c.toUpperCase()); | |
| // ^ crashes if str is undefined | |
| } | |
| ``` | |
| ### Why TypeScript Projects? | |
| The oh-my-opencode `category-skill-reminder` hook fires after 3+ tool calls and injects a fake `task()` code block into the LLM output. In TypeScript projects the LLM (Qwen 3.5, GLM-4.7, etc.) tends to call the `task` tool more frequently, and sometimes without the `subagent_type` field — triggering the crash. | |
| Even with `"disabled_hooks": ["category-skill-reminder"]` in oh-my-opencode config, the LLMs themselves generate `task()` calls without `subagent_type`, reproducing the crash. | |
| --- | |
| ## Upstream Fix | |
| This was reported as issue #13933 on the opencode repo and a fix was proposed in PR #13955 — adding a null guard to `titlecase()`. | |
| The correct fix in source is: | |
| ```typescript | |
| // src/util/locale.ts | |
| function titlecase(str: string | undefined): string { | |
| if (!str) return ""; | |
| return str.replace(/\b\w/g, (c) => c.toUpperCase()); | |
| } | |
| ``` | |
| Or alternatively, the null guard at the call site in `run.ts`: | |
| ```typescript | |
| Locale.titlecase(info.input.subagent_type ?? "") | |
| ``` | |
| --- | |
| ## Binary Patch (Workaround for v1.2.6) | |
| Since opencode is distributed as a compiled bun binary and v1.2.6 does not include the fix, the following in-place binary patch was applied to add a null guard without changing the file size. | |
| ### Patch Script | |
| ```python | |
| #!/usr/bin/env python3 | |
| """ | |
| Same-size binary patch for opencode v1.2.6 | |
| Adds null guard to the embedded titlecase() function. | |
| Tested on: /root/.opencode/bin/opencode (153,940,481 bytes) | |
| """ | |
| import sys | |
| binary_path = '/root/.opencode/bin/opencode' | |
| # Original function (91 bytes, including trailing newline) | |
| old = ( | |
| b'function titlecase(str3) {\n' | |
| b' return str3.replace(/\\b\\w/g, (c2) => c2.toUpperCase());\n' | |
| b' }\n' | |
| ) | |
| # Patched function (must be exactly same length = 91 bytes) | |
| # Returns "" for falsy input instead of crashing | |
| # Trailing spaces inside function body act as padding to maintain byte count | |
| new = ( | |
| b'function titlecase(str3){return str3?str3.replace(/\\b\\w/g,c2=>c2.toUpperCase()):""; }\n' | |
| ) | |
| print(f"Old pattern length: {len(old)} bytes") | |
| print(f"New pattern length: {len(new)} bytes") | |
| if len(old) != len(new): | |
| print(f"ERROR: Length mismatch! Cannot do same-size patch.") | |
| sys.exit(1) | |
| # Backup | |
| import shutil | |
| shutil.copy2(binary_path, binary_path + '.bak') | |
| print(f"Backup saved to {binary_path}.bak") | |
| with open(binary_path, 'rb') as f: | |
| data = f.read() | |
| count = data.count(old) | |
| print(f"Found {count} occurrence(s) of the titlecase function pattern") | |
| if count == 0: | |
| print("ERROR: Pattern not found in binary!") | |
| sys.exit(1) | |
| if count > 1: | |
| print(f"WARNING: Found {count} occurrences - will patch all of them") | |
| patched = data.replace(old, new) | |
| with open(binary_path, 'wb') as f: | |
| f.write(patched) | |
| print(f"Patch applied successfully! Patched {count} occurrence(s).") | |
| print(f"File size: {len(patched)} bytes (unchanged: {len(data) == len(patched)})") | |
| ``` | |
| ### Before vs After | |
| **Before (crashes on undefined):** | |
| ```javascript | |
| function titlecase(str3) { | |
| return str3.replace(/\b\w/g, (c2) => c2.toUpperCase()); | |
| } | |
| ``` | |
| **After (null-safe, same 91 bytes):** | |
| ```javascript | |
| function titlecase(str3){return str3?str3.replace(/\b\w/g,c2=>c2.toUpperCase()):""; } | |
| ``` | |
| ### Applying the Patch | |
| ```bash | |
| # Backup first (the script does this automatically) | |
| cp /root/.opencode/bin/opencode /root/.opencode/bin/opencode.bak | |
| # Run patch | |
| python3 patch_opencode.py | |
| # Verify | |
| # Old pattern length: 91 bytes | |
| # New pattern length: 91 bytes | |
| # Backup saved to /root/.opencode/bin/opencode.bak | |
| # Found 1 occurrence(s) of the titlecase function pattern | |
| # Patch applied successfully! Patched 1 occurrence(s). | |
| # File size: 153940481 bytes (unchanged: True) | |
| ``` | |
| --- | |
| ## Verification | |
| After patching, running opencode in a TypeScript project with multiple tool calls and sub-agent delegation (via oh-my-opencode's Sisyphus orchestration) completed successfully without any `TypeError`. | |
| **Test command:** | |
| ```bash | |
| cd /path/to/ts-project && opencode run "list all files and read package.json and tsconfig.json" | |
| ``` | |
| **Result:** Agent ran 10+ tool calls, spawned Sisyphus-Junior sub-agent, read multiple config files — exit code 0, no crash. | |
| --- | |
| ## Environment | |
| | Component | Version | | |
| |-----------|---------| | |
| | opencode | v1.2.6 (anomalyco/opencode fork) | | |
| | oh-my-opencode | v3.7.3 | | |
| | Models used | Qwen 3.5 (397B), GLM-4.7, MiniMax M2.1 via NVIDIA NIM | | |
| | OS | Linux (Ubuntu 24.04) | | |
| | Platform | bun-compiled binary | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment