惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MyScale Blog
MyScale Blog
人人都是产品经理
人人都是产品经理
云风的 BLOG
云风的 BLOG
小众软件
小众软件
F
Fortinet All Blogs
爱范儿
爱范儿
WordPress大学
WordPress大学
N
Netflix TechBlog - Medium
Recent Announcements
Recent Announcements
Google DeepMind News
Google DeepMind News
C
Check Point Blog
博客园 - 聂微东
D
Docker
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
aimingoo的专栏
aimingoo的专栏
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
A
About on SuperTechFans
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
B
Blog
宝玉的分享
宝玉的分享
Jina AI
Jina AI
H
Hackread – Cybersecurity News, Data Breaches, AI and More

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix: harden Mac gateway transport selection · openclaw/op...
steipete · 2026-05-18 · via Recent Commits to openclaw:main

@@ -16,6 +16,32 @@ final class RemotePortTunnel: @unchecked Sendable {

1616

let localPort: UInt16?

1717

private let stderrHandle: FileHandle?

181819+

private final class StderrCapture: @unchecked Sendable {

20+

private let lock = NSLock()

21+

private var text = ""

22+

private let limit = 4096

23+24+

func append(_ chunk: String) {

25+

let trimmed = chunk.trimmingCharacters(in: .whitespacesAndNewlines)

26+

guard !trimmed.isEmpty else { return }

27+

self.lock.lock()

28+

defer { self.lock.unlock() }

29+

if !self.text.isEmpty {

30+

self.text += "\n"

31+

}

32+

self.text += trimmed

33+

if self.text.count > self.limit {

34+

self.text = String(self.text.suffix(self.limit))

35+

}

36+

}

37+38+

func snapshot() -> String {

39+

self.lock.lock()

40+

defer { self.lock.unlock() }

41+

return self.text.trimmingCharacters(in: .whitespacesAndNewlines)

42+

}

43+

}

44+1945

private init(process: Process, localPort: UInt16?, stderrHandle: FileHandle?) {

2046

self.process = process

2147

self.localPort = localPort

@@ -93,6 +119,7 @@ final class RemotePortTunnel: @unchecked Sendable {

93119

let pipe = Pipe()

94120

process.standardError = pipe

95121

let stderrHandle = pipe.fileHandleForReading

122+

let stderrCapture = StderrCapture()

9612397124

// Consume stderr so ssh cannot block if it logs.

98125

stderrHandle.readabilityHandler = { handle in

@@ -106,6 +133,7 @@ final class RemotePortTunnel: @unchecked Sendable {

106133

.trimmingCharacters(in: .whitespacesAndNewlines),

107134

!line.isEmpty

108135

else { return }

136+

stderrCapture.append(line)

109137

Self.logger.error("ssh tunnel stderr: \(line, privacy: .public)")

110138

}

111139

process.terminationHandler = { _ in

@@ -114,7 +142,11 @@ final class RemotePortTunnel: @unchecked Sendable {

114142115143

try process.run()

116144117-

try await Self.waitForListener(process: process, localPort: localPort, stderrHandle: stderrHandle)

145+

try await Self.waitForListener(

146+

process: process,

147+

localPort: localPort,

148+

stderrHandle: stderrHandle,

149+

stderrCapture: stderrCapture)

118150119151

// Track tunnel so we can clean up stale listeners on restart.

120152

Task {

@@ -131,12 +163,13 @@ final class RemotePortTunnel: @unchecked Sendable {

131163

private static func waitForListener(

132164

process: Process,

133165

localPort: UInt16,

134-

stderrHandle: FileHandle) async throws

166+

stderrHandle: FileHandle,

167+

stderrCapture: StderrCapture) async throws

135168

{

136169

let deadline = Date().addingTimeInterval(6)

137170

repeat {

138171

if !process.isRunning {

139-

let stderr = Self.drainStderr(stderrHandle)

172+

let stderr = Self.drainStderr(stderrHandle, captured: stderrCapture.snapshot())

140173

let msg = stderr.isEmpty ? "ssh tunnel exited before listening" : "ssh tunnel failed: \(stderr)"

141174

throw NSError(domain: "RemotePortTunnel", code: 4, userInfo: [NSLocalizedDescriptionKey: msg])

142175

}

@@ -152,7 +185,7 @@ final class RemotePortTunnel: @unchecked Sendable {

152185

} while Date() < deadline

153186154187

process.terminate()

155-

let stderr = Self.drainStderr(stderrHandle)

188+

let stderr = Self.drainStderr(stderrHandle, captured: stderrCapture.snapshot())

156189

let msg = stderr.isEmpty ? "ssh tunnel did not open local port \(localPort)" : "ssh tunnel failed: \(stderr)"

157190

throw NSError(domain: "RemotePortTunnel", code: 4, userInfo: [NSLocalizedDescriptionKey: msg])

158191

}

@@ -311,16 +344,27 @@ final class RemotePortTunnel: @unchecked Sendable {

311344

}

312345313346

private static func drainStderr(_ handle: FileHandle) -> String {

347+

self.drainStderr(handle, captured: "")

348+

}

349+350+

private static func drainStderr(_ handle: FileHandle, captured: String) -> String {

314351

handle.readabilityHandler = nil

315352

defer { try? handle.close() }

316353317354

do {

318355

let data = try handle.readToEnd() ?? Data()

319-

return String(data: data, encoding: .utf8)?

356+

let remaining = String(data: data, encoding: .utf8)?

320357

.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""

358+

if captured.isEmpty {

359+

return remaining

360+

}

361+

if remaining.isEmpty {

362+

return captured

363+

}

364+

return captured + "\n" + remaining

321365

} catch {

322366

self.logger.debug("Failed to drain ssh stderr: \(error, privacy: .public)")

323-

return ""

367+

return captured

324368

}

325369

}

326370