fix(windows): log the port-scan fall-through on the relay diagnostic stream

Checked where this code actually runs before trusting the log. `console.warn`
did reach a file, but relayLogLine is the right call and the reasoning is worth
recording.

`scanWindowsListeningPorts` runs only in the detached relay daemon: relay.ts
returns early for --connect and --orca-cli, so PortScanHandler is reached only
through runRelayDaemon, and both launchers start it detached with a log file
(POSIX `> relay.log 2>&1`, Windows `1>relay.log 2>relay.err.log` via
Win32_Process.Create). installRelayLogRotation then wraps both streams into
relay.log, which is the file the documented diagnostics tail reads. Verified by
installing the real rotation over a temp path and reading the file back.

So the line surfaced -- but untimestamped, in a log whose format exists so
reconnect flaps can be correlated with the events around them (#7773).
relayLogLine is that format and the relay idiom in 41 other places, and
"since when has this host been stuck on PowerShell" is most of what this line
is for. The test spies on process.stderr to pin the stream and the ISO stamp
rather than just asserting something was called, since a fall-through logged
somewhere unread is the failure being guarded against.

Also fixes a comment that ended its own block early: `relay-*/relay.log` in a
doc comment contains `*/`.
This commit is contained in:
Orca Worker
2026-09-01 00:37:53 -07:00
parent 77940e54cc
commit 3fc2ed8b23
2 changed files with 45 additions and 2 deletions
+32 -1
View File
@@ -14,7 +14,10 @@ import {
__setWindowsProcessTreeLoaderForTests,
resetWindowsProcessTableForTests
} from '../main/windows/windows-process-table'
import { scanWindowsListeningPorts } from './windows-port-scan'
import {
resetWindowsPortScanDiagnosticsForTests,
scanWindowsListeningPorts
} from './windows-port-scan'
type Spec = {
program: string
@@ -85,6 +88,7 @@ function specs(): Spec[] {
describe('scanWindowsListeningPorts', () => {
beforeEach(() => {
runProcessMock.mockReset()
resetWindowsPortScanDiagnosticsForTests()
resetWindowsProcessTableForTests()
__setWindowsProcessTreeLoaderForTests(
nativeTable([
@@ -310,6 +314,33 @@ describe('scanWindowsListeningPorts', () => {
expect(getAllProcesses).not.toHaveBeenCalled()
})
// The relay daemon's stderr is what installRelayLogRotation routes into
// relay.log, so a fall-through logged anywhere else is a fall-through nobody
// can diagnose. Pin the stream, not just the fact that something was called.
it('reports leaving the native path on the relay diagnostic stream, once', async () => {
const lines: string[] = []
const stderr = vi
.spyOn(process.stderr, 'write')
.mockImplementation((chunk: string | Uint8Array) => {
lines.push(String(chunk))
return true
})
try {
runProcessMock.mockResolvedValue(ok(''))
await scanWindowsListeningPorts()
await scanWindowsListeningPorts()
} finally {
stderr.mockRestore()
}
const reported = lines.filter((line) => line.includes('[ports] netstat unusable'))
expect(reported).toHaveLength(1)
expect(reported[0]).toContain('no listening row parsed')
// relayLogLine's ISO stamp: an unplaceable line cannot be read against the
// reconnect flaps around it.
expect(reported[0]).toMatch(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /)
})
it('treats a netstat timeout as unanswered and falls through', async () => {
runProcessMock
.mockResolvedValueOnce({
+13 -1
View File
@@ -7,6 +7,7 @@ import {
import { getProcessOutputFields } from '../shared/process-output-field-scanner'
import type { DetectedPort } from './port-scan-handler'
import { buildRelayCommandEnv } from './relay-command-env'
import { relayLogLine } from './relay-diagnostic-log'
const SYSTEM_PORTS_TO_EXCLUDE = new Set([22])
const MAX_DETECTED_PORTS = 50
@@ -91,13 +92,24 @@ let reportedNetstatUnusable = false
* Both fall-throughs are permanent when they are wrong — the host stays on the
* PowerShell payload, or on nothing, for the life of the relay — and the scan
* repeats every 12-30s, so this logs one line rather than a stream.
*
* Through relayLogLine, not console.warn: this only ever runs in the detached
* daemon, whose stderr installRelayLogRotation routes into the relay.log that
* the remote-diagnostics tail reads. An untimestamped line in that file cannot
* be placed against the reconnect flaps around it (#7773), and "since when" is
* most of what this line is for.
*/
function reportWindowsNetstatUnusable(reason: string): void {
if (reportedNetstatUnusable) {
return
}
reportedNetstatUnusable = true
console.warn(`[ports] netstat unusable on this host (${reason}); falling back to PowerShell`)
relayLogLine(`[ports] netstat unusable on this host (${reason}); falling back to PowerShell`)
}
/** Test-only: re-arm the one-shot so each case can observe its own line. */
export function resetWindowsPortScanDiagnosticsForTests(): void {
reportedNetstatUnusable = false
}
/**