You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
OpenCode Desktop 2.0.28 - second file drop into the prompt is silently ignored #55216
Dragging files from the file tree sidebar into the prompt only works for the first file.
The first drop inserts @path as expected. Every drop after that does nothing at all - no error, nothing added to the prompt. So I can only ever attach one file by dragging.
I reported this for v1 as #39705 and opened #49185 to fix it, but that was closed because v1 is no longer taking PRs. I've reproduced it on 2.0.28, so it looks like the v2 composer still has it.
I checked the v2 source and the cause is still there: packages/app/src/composer/editor/actions.ts line 85 picks the replace range with text.slice(0, end).lastIndexOf("@"). Once the prompt already contains a mention, that finds the existing mention's "@", and insertMention() can't replace a range that spans a mention part, so it silently does nothing.
Plugins
None
OpenCode version
2.0.28
Steps to reproduce
Open OpenCode Desktop and start a session.
Drag a file from the file tree sidebar into the prompt box - @file is inserted.
Thanks for the detailed report and for pointing to the cause. I can confirm this on 2.0.28, and it still happens on the latest dev (e9def88).
I didn't run the desktop app. Instead I tested the composer code the drop handler uses. A file dropped from the tree goes through handleDrop and addPart, which calls addMention without a range. I called addMention three times that way: after the first call the prompt holds @src/a.ts, and the second and third calls leave it exactly as it was, with no error. As you described, the fallback range starts at the existing mention's @, and insertMention won't replace across a mention part, so nothing is inserted.
The test below reproduces it. Related: #49185 (your v1 fix PR), #39705 (the v1 report), and #48131 (a separate request to drag several files at once from the file tree).
Reproduction script (drop-mentions.test.ts)
// Reproduces #55216: the composer's drop handler (interaction.ts addPart) calls// draft.addMention(part) without a range for each file dropped into the prompt.import{expect,test}from"bun:test"import{createStore}from"solid-js/store"importtype{ComposerPersistedState}from"./src/composer/types"import{createComposerEditorActions}from"./src/composer/editor/actions"constfile=(path: string)=>({type: "file"asconst, path,content: `@${path}`,start: 0,end: 0})test("dropping a second file into the prompt adds a second mention",()=>{constdraft=createComposerEditorActions(createStore<ComposerPersistedState>({prompt: [{type: "text",content: "",start: 0,end: 0}],context: {items: []}}),)draft.addMention(file("src/a.ts"))// first dropconsole.log("after drop 1:",JSON.stringify(draft.state.prompt.map((p: any)=>p.content)),"cursor",draft.state.cursor)draft.addMention(file("src/b.ts"))// second dropconsole.log("after drop 2:",JSON.stringify(draft.state.prompt.map((p: any)=>p.content)),"cursor",draft.state.cursor)draft.addMention(file("src/c.ts"))// third dropconsole.log("after drop 3:",JSON.stringify(draft.state.prompt.map((p: any)=>p.content)),"cursor",draft.state.cursor)constpaths=draft.state.prompt.filter((p: any)=>p.type==="file").map((p: any)=>p.path)expect(paths).toEqual(["src/a.ts","src/b.ts","src/c.ts"])})
I reproduced the cause in the code rather than taking it from the report, and it is exactly where you pointed:
addMention() in packages/app/src/composer/editor/actions.ts builds its replacement range from a joined string — store().prompt.map((p) => ("content" in p ? p.content : "")).join("") — so a file mention's own @ is part of the text it searches. With no explicit range, start is text.slice(0, end).lastIndexOf("@"), which for a prompt that already holds @src/a.ts finds that mention's @.
The range is then handed to insertMention(), whose guard is if (part.type !== "text" || start < partStart || end > position) return [part] — only text parts can be replaced. A range spanning a mention part matches no part at all, every part is returned unchanged, and insertMention hands back a prompt identical to the input. Nothing throws, nothing is reported, and the drop is silently dropped. That is the whole defect: the fallback treats "an @ exists somewhere before the cursor" as "the user is typing a trigger", which is only true when that @ sits in a text part.
Planned fix, kept to the one behaviour:
only accept the searched @ as the replacement start when it lies inside a text part, i.e. a genuine typed trigger; otherwise fall back to inserting at the cursor, which is what a drop means
keep the explicit-range path (the autocomplete selection) exactly as it is
a test in the existing packages/app/src/composer/editor/actions.test.ts, driving addMention twice without a range and asserting the second call inserts rather than no-ops
I'll open it against the current default branch when it's ready. Noting for transparency that I saw your closed #49185 for the v1 path — this is the live file on v2, where the same fallback still stands, so a fresh PR is needed either way rather than a reopen.
Activity
opencode-agent commentedon Oct 13, 2026
Thanks for the detailed report and for pointing to the cause. I can confirm this on 2.0.28, and it still happens on the latest
dev(e9def88).I didn't run the desktop app. Instead I tested the composer code the drop handler uses. A file dropped from the tree goes through
handleDropandaddPart, which callsaddMentionwithout a range. I calledaddMentionthree times that way: after the first call the prompt holds@src/a.ts, and the second and third calls leave it exactly as it was, with no error. As you described, the fallback range starts at the existing mention's@, andinsertMentionwon't replace across a mention part, so nothing is inserted.The test below reproduces it. Related: #49185 (your v1 fix PR), #39705 (the v1 report), and #48131 (a separate request to drag several files at once from the file tree).
Reproduction script (drop-mentions.test.ts)
Run it:
bun drop-mentions.test.tsargszero commentedon Oct 13, 2026
I'd like to work on this.
I reproduced the cause in the code rather than taking it from the report, and it is exactly where you pointed:
addMention()inpackages/app/src/composer/editor/actions.tsbuilds its replacement range from a joined string —store().prompt.map((p) => ("content" in p ? p.content : "")).join("")— so a file mention's own@is part of the text it searches. With no explicitrange,startistext.slice(0, end).lastIndexOf("@"), which for a prompt that already holds@src/a.tsfinds that mention's@.The range is then handed to
insertMention(), whose guard isif (part.type !== "text" || start < partStart || end > position) return [part]— only text parts can be replaced. A range spanning a mention part matches no part at all, every part is returned unchanged, andinsertMentionhands back a prompt identical to the input. Nothing throws, nothing is reported, and the drop is silently dropped. That is the whole defect: the fallback treats "an@exists somewhere before the cursor" as "the user is typing a trigger", which is only true when that@sits in a text part.Planned fix, kept to the one behaviour:
@as the replacement start when it lies inside a text part, i.e. a genuine typed trigger; otherwise fall back to inserting at the cursor, which is what a drop meansrangepath (the autocomplete selection) exactly as it ispackages/app/src/composer/editor/actions.test.ts, drivingaddMentiontwice without a range and asserting the second call inserts rather than no-opsI'll open it against the current default branch when it's ready. Noting for transparency that I saw your closed
#49185for the v1 path — this is the live file on v2, where the same fallback still stands, so a fresh PR is needed either way rather than a reopen.deepakajay0477 commentedon Oct 13, 2026
I reported this and already have the v2 port of #49185 ready, with a regression test that fails before the fix and passes after.
Happy to open it now, or defer to @argszero - whichever the maintainers prefer.
deepakajay0477 commentedon Oct 13, 2026
@argszero I have this ready to open right now - the v2 port of #49185 plus a regression test that fails before the fix and passes after.
Want me to go ahead, or would you rather take it? Happy either way, I just don't want us both building the same thing.