Skip to content

Either the < or <= operators are causing responses from the ai to get cut off. #23928

Closed as not planned

Description

@blankyblank

Description

This has been a recurring issue I've noticed for a bit now, and I think I've finally tracked down at least part of the cause.

Every once in a while in the response from the agent, it would seemingly randomly get cut off midway through outputing something. I noticed showing the ai "thinking" would never cause a problem, as well as the ai outputting shell commands. but during the actual response. It just get's cut off.

I also noticed, it almost always seemed to happen white it outputs either an if statement, or a loop of some kind (I'm writing c, but really I don't think that matters). Eventually I was able to see what it was trying to output directly when it stopped. tell it to try testing if that's what keeps stopping it, and outputting <= seemed to stop it every time before it could output the text regardless of what it was doing.

My current theory is that somehow <= or < is interacting with terminal escape sequences, and that is somehow causing it to stop. That or their is something broken within opencodes system that causes it to stop when those specific charecters are sent in a response. But whatever it is it's for sure some interaction going on with the < charecter specifically.

Sorry if this happens to be a duplicate, there are 4000 issues open it appears so there definitely isn't going to be any way for me to look through all of those.

Also one extra detail, I use st for my terminal normally. but the same issue happens when using kitty, and I believe foot as well. So I don't think it's the terminal that's causing it.

Plugins

none as far as I know

OpenCode version

1.14.20 (but this problem has persisted across versions)

Steps to reproduce

  1. have the ai output code, or a response with <= in it.

Screenshot and/or share link

Image Image

Operating System

void linux, and arch linux.

Terminal

st, foot, and kitty

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    on Apr 23, 2026
  2. truffle-dev commented on Apr 23, 2026

    @truffle-dev

    This looks like a downstream symptom of two upstream @opentui/core CodeRenderable streaming bugs altendky filed yesterday:

    • anomalyco/opentui#963: in the content setter, the _streaming && !_drawUnstyledText && _filetype branch stores the new content and returns without calling requestRender(). If the renderer is otherwise idle, no frame is ever scheduled and the text buffer holds stale content until something external (e.g. keypress) kicks a frame.
    • anomalyco/opentui#964: startHighlight() has four snapshot-mismatch early returns that also don't call requestRender(), so when newer content arrives faster than the async highlighter finishes, the aborted highlight leaves the renderer unscheduled.

    Code path in opencode HEAD 871789ce64f179d5efa3031afbce3789e27e99b5: TextPart at packages/opencode/src/cli/cmd/tui/routes/session/index.tsx:1470-1500 renders assistant text through <code filetype="markdown" drawUnstyledText={false} streaming={true}>, which maps to CodeRenderable with exactly the _streaming && !_drawUnstyledText && _filetype combination from #963.

    Why < / <= specifically trigger it is consistent with (but not proven by) this mechanism: tree-sitter-markdown-inline treats < as the start of an autolink or inline HTML tag and has to disambiguate based on what follows. A lone <= isn't valid for either shape, so the parse likely backtracks, lengthening the highlight-in-flight window and giving newer stream chunks more chance to win the snapshot race in #964. I didn't reproduce this in isolation, so this is a fit with the upstream root-cause, not a confirmed cause.

    Two consistent observations from the report:

    • Shell commands don't trigger it: ToolPart dispatches to per-tool renderers (Bash, Glob, Read, etc.) and bypasses CodeRenderable streaming entirely.
    • Terminal-independent (st / foot / kitty): consistent with the bug being in the render-scheduling layer rather than in escape-sequence handling.

    Weak spot: reasoning uses the same <code streaming filetype=markdown> wrapper at ReasoningPart:1458-1466 and the reporter says it's fine. If the mechanism is right, the difference is likely timing (shorter reasoning streams, or less code-heavy content giving less tree-sitter backtracking), but I don't have a clean read on that.

    If this fit holds, opencode#23928 can track through the two upstream issues rather than be fixed here. A defensive requestRender() call somewhere in the opencode TextPart boundary would be a workaround but the clean fix is in CodeRenderable.

  3. kzekiue commented on Apr 23, 2026

    @kzekiue

    i tried this locally and im pretty sure the problem isnt just the opencode feature flag or the terminal itself, it feels like something in the markdown rendering path

    what i saw while testing

    • if i render a < b using the experimental markdown path it breaks
    • if i render the same thing using the fallback code filetype markdown path it also breaks
    • but if i just print it as plain text its fine

    so yeah the experimental flag only changes which path gets used, but both markdown related paths still have the issue

    not 100 percent sure where exactly its breaking, but it feels like its happening a bit deeper, maybe somewhere in the markdown or rendering layer inside @OpenTui core rather than in opencode itself

  4. truffle-dev commented on Apr 23, 2026

    @truffle-dev

    That triangulation narrows it well. The common factor across both paths is CodeRenderable with streaming:true, drawUnstyledText:false, filetype:"markdown".

    The experimental <markdown> path maps to MarkdownRenderable, which internally constructs CodeRenderable via createMarkdownCodeRenderable in the @opentui/core 0.1.99 bundle:

    return new CodeRenderable(this.ctx, {
      ...
      filetype: "markdown",
      drawUnstyledText: false,
      streaming: true,
      ...
    });

    And applyMarkdownCodeRenderable sets renderable.content = content on the existing CodeRenderable, which is the exact setter branch from anomalyco/opentui#963 that early-returns without requestRender().

    The fallback <code filetype="markdown" streaming={true} drawUnstyledText={false}> path at packages/opencode/src/cli/cmd/tui/routes/session/index.tsx:1488-1496 is the same CodeRenderable config directly.

    Plain text being fine is consistent: no CodeRenderable, no _filetype, no stalling branch.

    Both paths converge on the same upstream bug. The fix site is CodeRenderable.content setter and startHighlight() in opentui, not anything in opencode.

  5. mrrewilh commented on Apr 27, 2026

    @mrrewilh
    TaskListViewModel.cs:
    
    public partial class TaskListViewModel : ViewModelBase
    {
        [ObservableProperty]
        private Observable
    

    Add to models/game.rs → extend Catalog:

    // In models/game.rs - Catalog impl
    pub fn get_game(&self, package_name: &str) -> Option
    

    This happens on almost every message.

  6. truffle-dev commented on Apr 27, 2026

    @truffle-dev

    opencode v1.14.27 bumped @opentui/core to 0.1.105 (commit a3128e3, ~17h ago), which is the first release line that ships the fix from anomalyco/opentui#965 — "add missing requestRender() in CodeRenderable streaming content setter," merged as 5e20a2e and tagged in opentui 0.1.103.

    @mrrewilh, what does opencode --version print? If it's v1.14.27 or later and the bug still reproduces with Observable<...> / Option<...> truncation, the upstream patch isn't sufficient and there's a second code path missing a render trigger.

  7. mrrewilh commented on Apr 27, 2026

    @mrrewilh

    opencode v1.14.27 bumped @opentui/core to 0.1.105 (commit a3128e3, ~17h ago), which is the first release line that ships the fix from anomalyco/opentui#965 — "add missing requestRender() in CodeRenderable streaming content setter," merged as 5e20a2e and tagged in opentui 0.1.103.

    @mrrewilh, what does opencode --version print? If it's v1.14.27 or later and the bug still reproduces with Observable<...> / Option<...> truncation, the upstream patch isn't sufficient and there's a second code path missing a render trigger.

    Can confirm after the update everything works perfectly again.

  8. mrrewilh commented on Apr 28, 2026

    @mrrewilh
    Image

    No, out of nowhere it happened again.

  9. truffle-dev commented on Apr 28, 2026

    @truffle-dev

    Thanks for the follow-up. The fix from anomalyco/opentui#965 covered the missing requestRender() in the _streaming && !_drawUnstyledText && _filetype branch. The "works for a while, then happens again" pattern reads like a second code path missing a similar render trigger rather than a regression of the original bug.

    To triangulate, four pieces:

    • opencode --version (confirm current build — v1.14.27 through v1.14.29 all pin @opentui/core@0.1.105; opentui's v0.1.106 and v0.2.0 haven't shipped any CodeRenderable changes since the Explain usage of external references in AGENTS.md #965 fix).
    • The actual cut-off text as text rather than a screenshot, with the line it stops at.
    • Which model you're using (Claude / GPT / Gemini stream chunks differently and may exercise different paths).
    • Whether experimental.markdown = true is set, or you're on the fallback code filetype path.

    If the cut-off line consistently ends at the same kind of token (e.g. always inside < / <= / a code-fence open), that points the second missing render trigger at the same lexer-state branch. Different tokens each time suggests a timing-only race.

  10. mrrewilh commented on Apr 28, 2026

    @mrrewilh

    Thanks for the follow-up. The fix from anomalyco/opentui#965 covered the missing requestRender() in the _streaming && !_drawUnstyledText && _filetype branch. The "works for a while, then happens again" pattern reads like a second code path missing a similar render trigger rather than a regression of the original bug.

    To triangulate, four pieces:

    * `opencode --version` (confirm current build — v1.14.27 through v1.14.29 all pin `@opentui/core@0.1.105`; opentui's `v0.1.106` and `v0.2.0` haven't shipped any CodeRenderable changes since the [Explain usage of external references in AGENTS.md #965](https://github.com/anomalyco/opencode/pull/965) fix).
    
    * The actual cut-off text as text rather than a screenshot, with the line it stops at.
    
    * Which model you're using (Claude / GPT / Gemini stream chunks differently and may exercise different paths).
    
    * Whether `experimental.markdown = true` is set, or you're on the fallback `code` filetype path.
    

    If the cut-off line consistently ends at the same kind of token (e.g. always inside < / <= / a code-fence open), that points the second missing render trigger at the same lexer-state branch. Different tokens each time suggests a timing-only race.

    • opencode version:

    ❯ opencode --version
    1.14.29

    • Model: MiniMax M2.5 Free

    • experimental.markdown: default one i didnt edit any json or config.

    • Cut-off output:


    Let me create a comprehensive plan. // This is the thinking part (the gray area)

    Based on research, here's my plan:

    Plan: Core Search, Filter & VRDB Integration

    1. Current VR Src Data (already in core)
      Field
      name, package_name
      version, size_mb, downloads
      rating, rating_count
      Missing from VR Src: developer, genres, description, images, languages, comfort level

    1. New VR Src Functions (fast, in-memory)
      core/src/catalog/
      ├── loader.rs # existing - load catalog
      ├── search.rs # NEW - search/filter functions
      └── mod.rs
      Functions:
    • `Catalog::search(&self, query: &str
      ▣ Plan · MiniMax M2.5 Free · 35.4s

    Copied from the TUI.

  11. truffle-dev commented on Apr 28, 2026

    @truffle-dev

    Thanks for the version and cut-off text. Two updates from my earlier note:

    Correction: OPENCODE_EXPERIMENTAL_MARKDOWN defaults to true — the flag uses !falsy(...) in packages/core/src/flag/flag.ts, so an unset env var resolves to true. At default config you're rendering through MarkdownRenderable, not the <code filetype="markdown"> fallback. The fix in opentui#965 was on CodeRenderable.content setter, which is a different layer than the one your repro is exercising. That was my read miss.

    MarkdownRenderable parses incrementally and creates child renderables per block (one CodeRenderable per markdown block, separate ones for fenced code). With streaming={true} it keeps the trailing 2 tokens "unstable" until more content arrives. The TextPart in opencode hardcodes streaming={true} and never flips it to false, so at end-of-stream the trailing unstable tokens stay unstable indefinitely.

    One focused diagnostic to split where the text went:

    After a cut-off, does pressing any key or scrolling refresh the display to show the missing text?

    • Yes → text reached the buffer but the final paint never happened (missing render trigger on the trailing-unstable boundary, or in a child renderable).
    • No → text never made it into the buffer (parser dropped it, or set content short-circuited somewhere).

    If you can also paste the complete text after streaming finishes (let it sit a moment, then copy from the TUI scrollback), that distinguishes "final display cut off" from "final buffer cut off".

  12. mrrewilh commented on Apr 29, 2026

    @mrrewilh

    No, I'm one million percent sure i can't see missing text. For example;


    VRDB URL pattern: https://vrdb.app/game/{slug}/{id}
    - Example: https://vrdb.app/game/beat-saber/2448060205267927
    ---
    Implementation TODO List (For Myself)
    ┌─────────────────────────────────────────────────────────────┐
    │ TASK 1: Extend Game Model                                   │
    ├─────────────────────────────────────────────────────────────┤
    - Add vrdb_id: Option
    

    ▣ Plan · MiniMax M2.5 Free · 41.0s


    This was copied from TUI, after i left session with exit command then, continued with opencode -s ses_bla_bla
    Also in case if it matters, first it stops for a second. Like something happened (?) then it ends.
    @truffle-dev

  13. 8 remaining items

  14. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  15. truffle-dev commented on May 3, 2026

    @truffle-dev

    The SQL output settles where the content goes: storage row matches the TUI cut-off character-for-character, so the stream/storage write path isn't dropping chunks — opencode is persisting exactly what the model returned, and rendering exactly that.

    What I think is happening on those plans: the cut-off site (fn fetch_game(&self, game_id: &str mid-line, mid-token) is the shape you'd expect when the model hits its output-token limit. MiniMax M2.5 Free's per-request output cap is well under opencode's 32k default, so on a long plan the response truncates mid-content and the stream finishes with finishReason: "length". opencode then writes message.finish = "length", the message is treated as final, and the TUI shows the truncated text without surfacing the cause. The "stops for a second, then ends" pause matches the provider closing the stream after the limit is reached, not a render race.

    There's nothing on the opencode side that can recover the missing tokens (the model never emitted them), but the cause should at least be visible. Opened #25557 to add a non-stop finish-reason indicator next to the existing · interrupted suffix in the assistant header — for length / content-filter / error you'd see e.g. ▣ Plan · MiniMax M2.5 · 41.0s · truncated by length limit. Same suffix is added to the markdown transcript export.

    Workaround on your side until/if MiniMax raises the free-tier cap: switch to a model with a higher output cap for long plans (Claude / Sonnet, Gemini 2.5 Pro, GPT-4o all have higher per-request output budgets than MiniMax M2.5 Free), or shorten the prompt so the response fits.

  16. mrrewilh commented on May 3, 2026

    @mrrewilh

    After the latest cut-off, i prompted minimax to continue.

    VRDB implemented as swappable module - not coupled to VRSrc:
    pub trait MetadataSource {
        fn fetch_game(&self, game_id: &str
    ▣  Plan · MiniMax M2.5 Free · 3m 12s
    
    continue. Message got cutted off. // my prompt
    
    
    Thinking: The user wants me to continue the implementation plan. Let me continue from where I left off - I need to finish documenting the VRDB metadata fields, download strategy, and create the full TODO list.
    Since I'm in read-only/planning mode, I should just complete the plan document and present it to the user for their approval.
    I'll complete the implementation plan now:
    ---
    3. Download Strategy (via @explore agent research)
    Recommended: ugi crate
    Feature	Status
    Parallel chunk downloads	✅ HTTP Range requests
    Resume capability	✅ ETag/Last-Modified
    Rate limiting	✅ Token-bucket
    Integrity verification	✅ SHA256, Blake3
    Progress callbacks	✅ For UI
    Cross-platform	✅ Linux/Windows
    No external binaries	✅ Pure Rust
    Runtime	✅ Tokio-compatible
    Alternative: Custom reqwest implementation if more control needed.
    ---
    4. Archive Extraction
    Already implemented with sevenz-rust:
    sevenz_rust::decompress_file_with_password(archive_path, output_dir, password.into());
    Supports:
    - ✅ Password-protected .7z archives
    - ✅ AES-256 encryption
    - ✅ Cross-platform (pure Rust)
    ---
    5. Full Implementation TODO
    #	Task	Priority	Notes
    1	Add VRSrc search to Game model	search(query) → `	 
    ▣  Plan · MiniMax M2.5 Free · 8.9s
    
    

    As you can see, it cuts on short prompt and even short answer.

    Query for this session;

    [rewilh@buzul-x8664 ~]$ opencode db "SELECT json_extract(data, '$.text') AS text FROM part WHERE session_id = 'ses_235d4daafffedAM5H1sjFYCDVN' AND json_extract(data, '$.type') = 'text' ORDER BY id DESC LIMIT 3" --format json
    [
      {
        "text": "\n\nI'll complete the implementation plan now:\n\n---\n\n### 3. Download Strategy (via @explore agent research)\n\n**Recommended: `ugi` crate**\n\n| Feature | Status |\n|---------|--------|\n| Parallel chunk downloads | ✅ HTTP Range requests |\n| Resume capability | ✅ ETag/Last-Modified |\n| Rate limiting | ✅ Token-bucket |\n| Integrity verification | ✅ SHA256, Blake3 |\n| Progress callbacks | ✅ For UI |\n| Cross-platform | ✅ Linux/Windows |\n| No external binaries | ✅ Pure Rust |\n| Runtime | ✅ Tokio-compatible |\n\n**Alternative:** Custom reqwest implementation if more control needed.\n\n---\n\n### 4. Archive Extraction\n\nAlready implemented with `sevenz-rust`:\n\n```rust\nsevenz_rust::decompress_file_with_password(archive_path, output_dir, password.into());\n```\n\nSupports:\n- ✅ Password-protected .7z archives\n- ✅ AES-256 encryption\n- ✅ Cross-platform (pure Rust)\n\n---\n\n### 5. Full Implementation TODO\n\n| # | Task | Priority | Notes |\n|---|------|----------|-------|\n| 1 | Add VRSrc search to Game model | `search(query)` → `"
      },
      {
        "text": "continue. Message got cutted off."
      },
      {
        "text": "\n\n---\n\n## Implementation Plan: Game Search, Metadata, and Download\n\n### 1. VRSrc Game List Audit\n\n**Current Structure (GameList.txt - 9 fields):**\n\n| # | Field | Example |\n|---|-------|---------|\n| 1 | Game Name | `10 Seconds` |\n| 2 | Release Name | `10 Seconds v3+1.0 -VRP v76` |\n| 3 | Package Name | `com.ZynkSoftware.SecondsMatter` |\n| 4 | Version Code | `3` |\n| 5 | Last Updated | `2026-04-15 20:45 UTC` |\n| 6 | Size (MB) | `107` |\n| 7 | Downloads | `83.84` |\n| 8 | Rating | `0` |\n| 9 | Rating Count | `0` |\n\n**Status:** Minimal metadata - missing description, genres, tags, comfort level, languages, etc.\n\n---\n\n### 2. VRDB Integration\n\n**URL Pattern:** `https://vrdb.app/game/{slug}/{id}`\n\n**Example:** `https://vrdb.app/game/beat-saber/2448060205267927`\n\n**Available Metadata Fields:**\n\n| Category | Fields |\n|----------|--------|\n| **Core** | Name, Description, Genres, Release Date, Developer, Publisher |\n| **Platform** | Platforms (Quest 2/3/Pro/3S), Cross-Buy, Mixed Reality |\n| **Ratings** | Rating (1-5★), Rating Count, Review Text |\n| **Technical** | Version, Supported Controllers, Player Modes, Languages |\n| **Comfort** | Comfort Level (Comfortable → Intense), Motion Sickness |\n| **Content** | Tags, Game Modes, Playtime Estimates |\n| **Pricing** | Current Price, Price History |\n\n**Architecture (per AGENTS.md):**\n\n```\nsources/\n├── mod.rs           # Source trait\n├── telegram.rs      # VRSrc (existing)  \n└── vrdb.rs          # NEW - VRDB as separate module\n```\n\nVRDB implemented as **swappable module** - not coupled to VRSrc:\n\n```rust\npub trait MetadataSource {\n    fn fetch_game(&self, game_id: &str"
      }
    ]
    [rewilh@buzul-x8664 ~]$
    
    
    
  17. truffle-dev commented on May 3, 2026

    @truffle-dev

    The continue-attempt reproducing the cut-off (and storage matching the TUI again) is good signal. It rules out "first response was unlucky" and points at a stable mechanism. It also undermines my earlier "model hit length" framing: both the long initial response and the short continue response cut off, which is the wrong shape for a fixed output-token budget.

    The next diagnostic lives one table up. part only stores the textual content; the finish reason and any error live on message.data. If you can run:

    SELECT
      json_extract(data, '$.role') AS role,
      json_extract(data, '$.finish') AS finish,
      json_extract(data, '$.error') AS error,
      json_extract(data, '$.tokens.output') AS output_tokens,
      json_extract(data, '$.cost') AS cost
    FROM message
    WHERE session_id = 'ses_235d4daafffedAM5H1sjFYCDVN'
      AND json_extract(data, '$.role') = 'assistant'
    ORDER BY id DESC
    LIMIT 5

    Three branches from the result:

    • finish = "length": model self-reported hitting its output cap. PR fix(tui): surface non-stop finish reasons in assistant header #25557 surfaces this in the TUI as · truncated by length limit. Underlying ask is "raise the model's output limit" (for MiniMax via OpenRouter, that's the upstream provider's max_tokens default).
    • finish = "error" and error populated: stream errored mid-flight. PR fix(tui): surface non-stop finish reasons in assistant header #25557 surfaces this as · ended with error, and the partial content stays visible.
    • finish is null/empty on the truncated rows: most likely the SSE stream was cut before any finish-step event arrived. opencode sets assistantMessage.finish only when it receives a finish-step event from the model (packages/opencode/src/session/processor.ts:470); the only fallback to "stop" sits in the structured-output branch (prompt.ts:1591). A network-level disconnect on free-tier MiniMax leaves finish undefined, the message renders without any suffix, and the truncation is invisible. PR fix(tui): surface non-stop finish reasons in assistant header #25557 does NOT yet cover this case.

    The "even short prompts and short answers" datapoint plus the consistent cut-offs really point at branch three. If your query confirms finish is null on the truncated rows, I'll send a follow-up tracking whether a finish-step ever arrived and surfacing · stream cut in the TUI for that case.

  18. mrrewilh commented on May 3, 2026

    @mrrewilh
  19. truffle-dev commented on May 3, 2026

    @truffle-dev

    Thanks for the query. The data rules out all three branches I pointed at: no length, no error, no null finish. Two rows ended stop (742 and 1194 output tokens), three ended tool-calls (65, 187, 331). The model is self-reporting normal completion every time. PR #25557 only attaches a suffix when finish is length, error, or content-filter, so it won't surface anything for these rows. The follow-up patch I floated for "stream cut before finish-step" doesn't apply either, because the finish event clearly arrived.

    That changes the question. Two failure modes still fit the "feels cut off" symptom:

    1. Model-quality terseness on the stop rows. 742 visible tokens is roughly 500-800 words, substantive, but free-tier MiniMax sometimes wraps a complex ask in a short summary instead of working through it. If the truncated message you screenshotted earlier is one of these stop rows, this is upstream behavior, not opencode dropping content. The diagnostic: pick the row with finish="stop" and output_tokens=742, run

      SELECT json_extract(data, '$.text') AS text, length(json_extract(data, '$.text')) AS chars
      FROM part
      WHERE message_id = (SELECT id FROM message WHERE session_id='ses_235d4daafffedAM5H1sjFYCDVN' AND json_extract(data,'$.tokens.output')=742 ORDER BY id DESC LIMIT 1)
        AND json_extract(data,'$.type')='text';

      If the text part holds a substantial response (a few thousand chars), the model wrote what it intended and the cut-off is perceptual. If the text part is much shorter than 742 tokens would predict, something is dropping content between the SSE stream and the part write. That's a real opencode bug worth tracking separately.

    2. Tool-loop interruption on the tool-calls rows. Each finish="tool-calls" row should be followed by tool execution and another assistant step. If the next step never fires (free-tier rate-limit between turns, OpenRouter reconnect, etc.), the conversation stalls on a short message and looks cut off. To check, take the most recent finish="tool-calls" row's id and run

      SELECT json_extract(data,'$.role') AS role, json_extract(data,'$.finish') AS finish, id
      FROM message
      WHERE session_id='ses_235d4daafffedAM5H1sjFYCDVN' AND id > <that_id>
      ORDER BY id ASC LIMIT 5;

      If there's no follow-up assistant or tool message, the tool loop bailed silently after the model emitted calls. That's a different fix path (heartbeat or retry on tool-loop continuation), not a finish-suffix.

    Either result narrows the next move. Apologies for the wrong branches in the first reply. Your continue-attempt evidence pushed me one table up, and the data pushed me one layer further than that.

  20. mrrewilh commented on May 3, 2026

    @mrrewilh

    I reverted those messages. It still happens but i was trying to continue. Its not possible tho. Sorry for this, cant go back to them right now.

    Image

    query-2.json

    The fixed queries (as .json) for my current session (?);

    [
       {
          "text": "\n\n**Fixed issues:**\n- ✅ Telegram auto-fetch now works (CLI auto-fetches credentials automatically)\n- ✅ Search/filter/sort added to Catalog (`search()`, `filter_by_rating()`, `sort_by_downloads()`, etc.)\n- ✅ Extraction error properly handled with `.map_err()`\n- ✅ Fixed Telegram HTML parsing (uses",
          "chars": 296
       }
    ]
    

    For tool-loop

    [
       {
          "role": "assistant",
          "finish": "stop",
          "id": "msg_deda47141001K5m022DJDGw1k9"
       }
    ]
    

    Also a brief query about all messages

    Image

    query-7.json

  21. truffle-dev commented on May 3, 2026

    @truffle-dev

    Two things land from the new queries:

    1. Hypothesis (a) confirmed. The stop row reports 172 output tokens. English averages roughly 4 chars per token, so 172 tokens predicts something around 600 to 800 chars. The stored text part is 296 chars and ends in the middle of a word (Fixed Telegram HTML parsing (uses). That mid-word terminator is the smoking gun. If the model had naturally stopped there, the last token would be a punctuation or sentence end, not an open paren followed by a verb.

    2. Hypothesis (b) ruled out. Every assistant row in query-7.json has a populated finish (one stop plus nine tool-calls). The tool loop is advancing normally, no silent bail.

    So the question is where the drop happens between the model emitting and the part being saved. opencode's text accumulator is at packages/opencode/src/session/processor.ts:533-544:

    case "text-delta":
      if (!ctx.currentText) return
      ctx.currentText.text += value.text
      ...

    It just concatenates whatever arrives, no filtering. So the deltas opencode sees are the deltas the AI SDK demuxed from the SSE stream, which are the deltas the OpenRouter free-tier proxy forwarded from MiniMax. The mid-word cut plus the finish="stop" event arriving cleanly afterward fits one shape best: the upstream proxy truncated the model's actual output and emitted a synthetic finish_reason="stop" event. From opencode's perspective the stream looked complete, so it stored what it got and rendered no truncation suffix.

    The original < / <= correlation may be a related but distinct symptom of the same upstream behavior. The current session's truncated text contains no <, and it still cuts mid-word. Whatever the proxy is doing, it's not strictly conditional on those characters.

    Two opencode-side fixes worth considering separately from PR #25557:

    • Heuristic mismatch warning. When output_tokens exceeds length(text part) / 3 by some margin (say 2x), surface · response may be truncated upstream in the assistant header. Catches synthesized-stop cases that PR fix(tui): surface non-stop finish reasons in assistant header #25557's length/error/content-filter switch can't see.
    • Stream-event delta count audit. Add an opt-in debug log that records the count of text-delta events received and the cumulative chars per part. If the count looks plausible (e.g. 50 deltas averaging ~6 chars) the truncation is upstream; if there's a sudden gap (say 50 deltas then nothing for 10s before finish-step), the truncation is between AI SDK and opencode.

    Happy to ship either if the maintainers want one. The first is a small TUI patch on top of #25557; the second is a few logs in processor.ts behind a DEBUG=opencode:stream flag.

    For your immediate situation: a paid-tier OpenRouter key or a different provider would test the upstream-truncation theory directly. If the same prompt completes cleanly on a non-free MiniMax key (or any other model), the proxy is the culprit and the fix path is upstream. If it still truncates everywhere, that's stronger signal for an opencode-side stream parsing issue and the audit log would catch it.

  22. mrrewilh commented on May 3, 2026

    @mrrewilh

    Thanks.

    I thought this was about me but this wasn't a issue about < or <= operators at all for me.

    So if @blankyblank is good to go, we can close the issue. If a cut-off happens again i can open a new issue.

  23. truffle-dev commented on May 3, 2026

    @truffle-dev

    Sounds good. To leave a useful breadcrumb for anyone who lands here later: the diagnostic narrowed to opencode receiving stream events that look complete (text-deltas + a clean finish-step with reason="stop"), but the stored text is shorter than output_tokens predicts and ends mid-word. That fits an upstream proxy synthesizing a stop event after truncating the model's actual output, with opencode faithfully recording what arrived.

    Two opencode-side fixes that would surface this to users (separate from #25557, which only catches length/error/content-filter) if maintainers want them as actionable items:

    1. Heuristic mismatch warning in the assistant header when output_tokens exceeds length(text) / 3 by a margin (catches synthesized-stop cases).
    2. Opt-in DEBUG=opencode:stream log of text-delta event count and cumulative chars per part (lets a user distinguish upstream truncation from any AI-SDK-to-opencode delta drop).

    Happy to ship either as a follow-up PR if there's interest. Otherwise leaving the diagnostic here so the next person hitting this can skip the four rounds.

  24. github-actions commented on Jul 4, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

  25. blankyblank commented on Jul 5, 2026

    @blankyblank
    Author

    sorry just saw this.

    i haven't been noticing it lately. so it seems to have been resolved at some point. Not sure if it was fixed by some unrelated change, or something earlier after I reported the issue.

    while the issue was happening, it definitely wasn't getting cut off because of length. it would get caught off sometimes on the first sentence if i couldn't get the model to start again on the line after the > (or it might have been < ) occured.

    But like I said, it seems to have been resolved as of now.

  26. 3xecutablefile commented on Aug 22, 2026

    @3xecutablefile

    I have noticed this with the new 0x alpha model, which cuts off every 5 minutes or so

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions