Commit 14b8a9abb8

14b8a9abb840f47582ceca3a756cb9009e1340d6

parent: 3d76c278d1

Unsigned

cmc <hello@cleberg.net> · 2026-08-27 06:41 UTC

fix: stdin-carrying writes need --file - in argv

bodyFrom only reads stdin when argv says --file -; without it the
inline message wins and an empty one is a usage error. Verified against
production: issue comment with bare stdin returns exit 2 'empty
comment', with --file - it lands. Fixes mr comment, mr diff-comment
--reply, and issue comment, which all sent bare stdin.

Ref #11

Layout: unified · split

gitbay/Issues/IssueDetailViewModel.swift +2 −1
@@ -32,7 +32,8 @@ final class IssueDetailViewModel {
32 } 32 }
33 33
34 func comment(_ text: String) async { 34 func comment(_ text: String) async {
35 await perform(["issue", "comment"] + ref, stdin: text) 35 // stdin is only read when argv carries --file -.
36 await perform(["issue", "comment"] + ref + ["--file", "-"], stdin: text)
36 } 37 }
37 38
38 func close() async { 39 func close() async {
gitbay/MRs/MRDetailViewModel.swift +4 −3
@@ -59,8 +59,9 @@ final class MRDetailViewModel {
59 } 59 }
60 60
61 func comment(_ text: String) async { 61 func comment(_ text: String) async {
62 // Long text goes in stdin, the CLI's --file - discipline. 62 // Long text travels in stdin, but the server only reads it when
63 await perform(["mr", "comment"] + ref, stdin: text) 63 // argv says so: --file - is required, not implied.
64 await perform(["mr", "comment"] + ref + ["--file", "-"], stdin: text)
64 } 65 }
65 66
66 func merge(strategy: String? = nil) async { 67 func merge(strategy: String? = nil) async {
@@ -79,7 +80,7 @@ final class MRDetailViewModel {
79 80
80 func reply(to thread: ReviewThread, _ text: String) async { 81 func reply(to thread: ReviewThread, _ text: String) async {
81 await perform( 82 await perform(
82 ["mr", "diff-comment"] + ref + ["--reply", String(thread.id)], 83 ["mr", "diff-comment"] + ref + ["--reply", String(thread.id), "--file", "-"],
83 stdin: text 84 stdin: text
84 ) 85 )
85 } 86 }
gitbayTests/IssueBuildViewModelTests.swift +1 −1
@@ -87,7 +87,7 @@ struct IssueDetailViewModelTests {
87 let write = stub.seen[1] 87 let write = stub.seen[1]
88 #expect(write.method == "POST") 88 #expect(write.method == "POST")
89 let body = try #require(try JSONSerialization.jsonObject(with: write.body) as? [String: Any]) 89 let body = try #require(try JSONSerialization.jsonObject(with: write.body) as? [String: Any])
90 #expect(body["argv"] as? [String] == ["issue", "comment", "krz/gitbay", "11"]) 90 #expect(body["argv"] as? [String] == ["issue", "comment", "krz/gitbay", "11", "--file", "-"])
91 #expect(body["stdin"] as? String == "triaged from the phone") 91 #expect(body["stdin"] as? String == "triaged from the phone")
92 } 92 }
93 93
gitbayTests/MRViewModelTests.swift +2 −2
@@ -228,7 +228,7 @@ struct MRDetailViewModelTests {
228 228
229 let write = stub.seen[3] 229 let write = stub.seen[3]
230 let body = try #require(try JSONSerialization.jsonObject(with: write.body) as? [String: Any]) 230 let body = try #require(try JSONSerialization.jsonObject(with: write.body) as? [String: Any])
231 #expect(body["argv"] as? [String] == ["mr", "comment", "krz/gitbay", "7"]) 231 #expect(body["argv"] as? [String] == ["mr", "comment", "krz/gitbay", "7", "--file", "-"])
232 #expect(body["stdin"] as? String == "long review text\nwith lines") 232 #expect(body["stdin"] as? String == "long review text\nwith lines")
233 } 233 }
234 234
@@ -273,7 +273,7 @@ struct MRDetailViewModelTests {
273 let write = stub.seen[3] 273 let write = stub.seen[3]
274 let body = try #require(try JSONSerialization.jsonObject(with: write.body) as? [String: Any]) 274 let body = try #require(try JSONSerialization.jsonObject(with: write.body) as? [String: Any])
275 #expect(body["argv"] as? [String] == 275 #expect(body["argv"] as? [String] ==
276 ["mr", "diff-comment", "krz/gitbay", "7", "--reply", "3"]) 276 ["mr", "diff-comment", "krz/gitbay", "7", "--reply", "3", "--file", "-"])
277 #expect(body["stdin"] as? String == "because 5xx is transient") 277 #expect(body["stdin"] as? String == "because 5xx is transient")
278 } 278 }
279} 279}