Skip to content

tui: single-line file mention (lineStart == lineEnd, or "#N") attaches from that line to the end of the file #58033

Open

Description

@pawelbbdrozd

Description

A file mention that covers exactly one line is attached as "from that line to the end of the file" instead of one line.

The TUI drops the end of the range when it equals the start. Read on v2.0.32 and current dev, identical in both:

  • packages/tui/src/component/prompt/autocomplete.tsx (insertFileMention): endLine: input.lineEnd > input.lineStart ? input.lineEnd : undefined, so no end reaches the file URI.
  • packages/tui/src/prompt/parse.ts (parseFileLineRange): startLine < Number(match[2]) ? Number(match[2]) : undefined, so #10, #10- and #10-10 all parse to the same open-ended range.
  • packages/core/src/session/prompt.ts: .slice(resolved.start - 1, resolved.end) with end === undefined slices to the end of the file. With ?start=10&end=10 the server would return exactly one line, so only the TUI loses the value.

The mention label is built as path#start plus -end only when end is set, so a one-line mention and an open-ended one both render as #10 and cannot be told apart.

Steps to reproduce

  1. Run the v2.0.32 TUI in a project and connect an editor integration (lock file in ~/.claude/ide/<port>.lock, or OPENCODE_EDITOR_SSE_PORT).
  2. From the editor send at_mentioned with lineStart: 10 and lineEnd: 10 for a file with 286 lines.
  3. Send the prompt.

Result: the stored attachment URI is file:///.../file?start=10 (no end) and it contains lines 10-286 (277 lines). Expected: one line, or a mention that visibly says it is open-ended.

Possible directions

  • Treat lineEnd >= lineStart as a bounded range and always send end.
  • If "from line N to the end" is intended, render it differently (for example #10-) so that #N is not ambiguous.

Related, different code path: #42887 and the closed PR #47503 (single-line selection and enclosing symbol).

OpenCode version

2.0.32 (the same code is on dev at 4d59657)

Operating System

Linux (Manjaro)

Terminal

Ghostty with Zellij

Activity

  1. opencode-agent commented on Oct 22, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I can reproduce this on v2.0.32, and it still happens on the latest dev (c93aaae).

    What I did, in a project with a 286-line big.txt:

    1. Typed @big.txt#10-10 in the prompt and pressed Tab to accept the autocomplete entry. The chip shows big.txt#10.
    2. Sent the prompt with some text.
    3. Checked the request sent to the model: it contained lines 10 to 286 (277 lines), not only line 10.

    The recording and screenshot below show it, and the script below reproduces it. I tested the typed #10-10 mention rather than the editor at_mentioned path, but both drop the end line through the same lineEnd > lineStart / start < end checks you pointed out in insertFileMention and parseFileLineRange.

    recording.mp4

    Screenshot

    Reproduction script (drive.ts)
    // Repro for #58033: a single-line mention (@big.txt#10-10) attaches lines 10..EOF.
    import { Effect, Stream } from "effect"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    const lines = Array.from({ length: 286 }, (_, i) => `LINE_${i + 1}_MARK`).join("\n") + "\n"
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        project: { git: true, files: { "big.txt": lines } },
        tui: { recording: true, keypressOverlay: true },
      },
      ({ ui, llm }) =>
        Effect.gen(function* () {
          let body = ""
          yield* llm.serve((request: any) => {
            body = JSON.stringify(request.body ?? request)
            return Stream.make(Llm.text("ok"))
          })
          yield* ui.type("@big.txt#10-10")
          yield* Effect.sleep("1500 millis")
          yield* ui.screenshot("autocomplete")
          yield* ui.press("tab")
          yield* Effect.sleep("500 millis")
          yield* ui.screenshot("mention-inserted")
          yield* ui.type(" summarize")
          yield* ui.enter()
          yield* ui.waitFor("ok")
          yield* Effect.sleep("1000 millis")
          yield* ui.screenshot("after-send")
          const sent = [...body.matchAll(/LINE_(\d+)_MARK/g)].map((m) => Number(m[1]))
          const uris = [...new Set(body.match(/file:\/\/[^"\\ ]+/g) ?? [])]
          yield* Effect.log(`file URIs in request: ${JSON.stringify(uris)}`)
          yield* Effect.log(
            `lines sent to model: count=${new Set(sent).size} min=${Math.min(...sent)} max=${Math.max(...sent)}`,
          )
          yield* Effect.log(new Set(sent).size > 1 ? "BUG: more than one line attached" : "OK: one line attached")
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions