left4me/docs/superpowers/plans/2026-05-06-l4d2-install-logging.md

160 lines
5.2 KiB
Markdown

# SteamCMD Install Logging & Buffering Fix Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Improve live feedback during the `install` operation by adding step markers to `SteamInstaller` and fixing Python subprocess buffering so output streams immediately.
**Architecture:** Modifies `l4d2host/process.py` and `l4d2web/services/host_commands.py` to add `flush=True` to `print()` statements for immediate pipeline throughput. Modifies `l4d2host/steam_install.py` to use `_emit_step` to log platform payload downloads.
**Tech Stack:** Python, subprocess
---
### Task 1: Fix process output buffering
**Files:**
- Modify: `l4d2host/process.py`
- Modify: `l4d2web/services/host_commands.py`
- [ ] **Step 1: Check existing test suite**
No failing test is required here because we are only modifying the `flush` parameter of `print()` inside existing pass-through functions, which are thoroughly covered by integration tests but unit-testing the buffering of `print` is notoriously flaky across OSes. We will just modify the code and run the existing suite.
- [ ] **Step 2: Add flush to l4d2host process**
Modify `emit_stderr_message` and `pump` inside `run_command` in `l4d2host/process.py`:
```python
def emit_stderr_message(line: str) -> None:
stderr_lines.append(line)
if on_stderr is not None:
on_stderr(line)
if passthrough:
print(line, file=sys.stderr, flush=True)
# ... inside pump ...
if passthrough:
print(line, file=output_stream, flush=True)
```
- [ ] **Step 3: Add flush to l4d2web host_commands**
Modify `emit_stderr_message` and `pump` inside `run_command` in `l4d2web/services/host_commands.py`:
```python
def emit_stderr_message(line: str) -> None:
stderr_lines.append(line)
if on_stderr is not None:
on_stderr(line)
if passthrough:
print(line, file=sys.stderr, flush=True)
# ... inside pump ...
if passthrough:
print(line, file=output_stream, flush=True)
```
- [ ] **Step 4: Run test to verify it passes**
Run: `pytest l4d2host/tests l4d2web/tests -q`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add l4d2host/process.py l4d2web/services/host_commands.py
git commit -m "fix(host): enforce flush=True to prevent pipeline block buffering"
```
---
### Task 2: Add step logging to SteamInstaller
**Files:**
- Modify: `l4d2host/steam_install.py`
- Modify: `l4d2host/tests/test_install.py`
- [ ] **Step 1: Write failing test**
In `l4d2host/tests/test_install.py`, add a test to verify `SteamInstaller` logs steps:
```python
def test_steam_installer_emits_steps(tmp_path: Path, monkeypatch) -> None:
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
monkeypatch.setattr("l4d2host.steam_install.run_command", lambda cmd, **kwargs: None)
steps: list[str] = []
from l4d2host.steam_install import SteamInstaller
SteamInstaller().install_or_update(on_stdout=steps.append)
assert steps == [
"Step: downloading windows platform payload...",
"Step: downloading linux platform payload...",
"Step: installation complete."
]
```
- [ ] **Step 2: Run test to verify it fails**
Run: `pytest l4d2host/tests/test_install.py -k test_steam_installer_emits_steps -q`
Expected: FAIL because `steps` is empty.
- [ ] **Step 3: Add step logs to SteamInstaller**
In `l4d2host/steam_install.py`, import `_emit_step`:
```python
from l4d2host.instances import _emit_step
```
Modify `install_or_update`:
```python
def install_or_update(
self,
*,
on_stdout: Callable[[str], None] | None = None,
on_stderr: Callable[[str], None] | None = None,
passthrough: bool = False,
should_cancel: Callable[[], bool] | None = None,
) -> None:
for platform in ("windows", "linux"):
_emit_step(f"downloading {platform} platform payload...", on_stdout, passthrough)
run_command(
[
self.steamcmd,
"+force_install_dir",
str(self.install_dir),
"+login",
"anonymous",
"+@sSteamCmdForcePlatformType",
platform,
"+app_update",
"222860",
"validate",
"+quit",
],
on_stdout=on_stdout,
on_stderr=on_stderr,
passthrough=passthrough,
should_cancel=should_cancel,
)
_emit_step("installation complete.", on_stdout, passthrough)
```
- [ ] **Step 4: Run test to verify it passes**
Run: `pytest l4d2host/tests/test_install.py -k test_steam_installer_emits_steps -q`
Expected: PASS
- [ ] **Step 5: Run full suite**
Run: `pytest l4d2host/tests l4d2web/tests -q`
Expected: PASS
- [ ] **Step 6: Commit**
```bash
git add l4d2host/steam_install.py l4d2host/tests/test_install.py
git commit -m "feat(host): add step logging to steam_install"
```