uttrflow / uttrflow/uttrflow-swift

The formatter's 3-second timeout never applies: the output is read to the end before the deadline is checked

Open
#579 0 comments 0 reactions 0 assignees View on GitHub
area:clipboard bug P2
Dominant language
Swift
Stars
4
Forks
17
Avg merge
3h 32m
Merged PRs (30d)
277

Description

## What happens

`SystemCodeFormatter.run` (`Sources/UttrflowClipboard/CodeFormatting+System.swift:36-73`) has a timeout, `KnownFormatter.timeout = 3` (`CodeFormatting.swift:52-53`), commented "a hung program must not take the panel". But the order of operations is:

```swift
stdin.fileHandleForWriting.write(Data(input.utf8)) // blocks until the tool reads it all
try? stdin.fileHandleForWriting.close()
let produced = try? stdout.fileHandleForReading.readToEnd() // blocks until the tool closes stdout
_ = try? stderr.fileHandleForReading.readToEnd()
let deadline = Date().addingTimeInterval(KnownFormatter.timeout) // only now
while process.isRunning && Date() < deadline { ... }
```

`readToEnd()` returns only when the formatter exits or closes its output. So the deadline starts after the formatter has already finished. A slow formatter's output is accepted however long it took, and a formatter that never exits blocks this task forever. There are two more problems:

- The whole clip is written to stdin before any output is read. A formatter that writes output while still reading input fills the 64 KB pipe and deadlocks against this write, and a clip can be up to 2 MB.
- stderr is drained only after stdout, so a tool that writes more than a pipe buffer of warnings blocks.

## Measured

The same function body, with the tool swapped for `/bin/sh -c 'cat >/dev/null; sleep 8; echo formatted'`: it **returned after 8.0 s with the output accepted**, against a 3 s timeout.

## Why it matters

Format is offered whenever a formatter is installed. If that tool hangs (waiting on a config lookup, a network fetch, or a first-run prompt), the sheet never appears and nothing says why. The user presses Format again, and each press leaks a blocked task and a child process.

## Acceptance criteria

- The deadline covers the whole run from `process.run()`. When it passes, the process is terminated and `nil` is returned, whatever the pipes are doing.
- stdin is written, and stdout and stderr are drained, concurrently (for example with `readabilityHandler`s or separate tasks), so neither a large clip nor a chatty tool can deadlock.
- A test in `Tests/UttrflowClipboardTests/` drives the same run logic against a stand-in executable, for example a script in a temporary directory behind an injectable tool URL. It shows that a tool which sleeps past the timeout returns `nil` within about the timeout, and that a 1 MB input round-trips through a tool that echoes as it reads.

Contributor guide

Open the contributing guide

Research direction

Start with SystemCodeFormatter.run in Sources/UttrflowClipboard/CodeFormatting+System.swift and the timeout definition in CodeFormatting.swift. Trace process.run(), stdin writing, and stdout/stderr draining, then inspect Tests/UttrflowClipboardTests/ for the test setup. Done means the timeout covers the whole run, pipes drain concurrently, and tests cover timeout termination and a 1 MB echo.

Written by the indexing model from the issue text.

Assessment

Tech stack
macos, swift
Domain
desktop, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.