# AutoProtocol Streamlit + CodexCLI service hardening

Use this reference when a local Streamlit transcription/protocol web app runs under `systemd --user` and calls CodexCLI or another user-installed CLI after STT.

## Trigger

- Web UI upload/transcription appears to run, then final protocol generation fails.
- The same CodexCLI command works in an interactive terminal but fails from the Streamlit service.
- Long meeting transcription has multiple stages where losing the intermediate transcript would waste time.

## Durable pattern

1. **Inspect service environment separately from the shell.** User services often have a minimal `PATH` and do not inherit `~/.local/bin`.
2. **Resolve CLI binaries robustly in code, not by assuming interactive shell PATH.**
   - Prefer an explicit env var such as `CODEX_BIN`.
   - Fall back to `shutil.which("codex")`.
   - Also check known user install locations such as `Path.home() / ".local/bin/codex"` and `/usr/local/bin/codex`.
3. **Pass a safe PATH to subprocesses.** Include `~/.local/bin` and `/usr/local/bin` before the existing service PATH.
4. **Save expensive intermediate output before the LLM step.** After WhisperX/STT succeeds, write a raw transcript `.docx` before calling CodexCLI for the final protocol. If the LLM step fails, the transcription is preserved and the user does not need to rerun GPU STT.
5. **Restart and verify the service.** Run syntax checks, a minimal systemd-like environment smoke for CLI resolution, a real small CodexCLI prompt if safe, then restart the user service and verify local/Tailnet HTTP 200.

## Example snippets

```python
def _resolve_codex_bin() -> str:
    configured = os.getenv("CODEX_BIN")
    candidates = [configured] if configured else []
    candidates.extend([
        "codex",
        str(Path.home() / ".local" / "bin" / "codex"),
        "/usr/local/bin/codex",
    ])
    for candidate in candidates:
        if not candidate:
            continue
        if Path(candidate).is_absolute() and Path(candidate).exists():
            return candidate
        resolved = shutil.which(candidate)
        if resolved:
            return resolved
    raise RuntimeError("CodexCLI nicht gefunden. Setze CODEX_BIN=... oder installiere codex.")
```

```python
subprocess.run(
    command,
    input=prompt,
    text=True,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    timeout=timeout,
    check=False,
    env={
        **os.environ,
        "PATH": f"{Path.home() / '.local' / 'bin'}:/usr/local/bin:{os.environ.get('PATH', '')}",
    },
)
```

## Verification commands

```bash
# Syntax/import safety
.venv/bin/python -m py_compile app.py autoprotocol.py llm_processor.py

# Simulate minimal systemd user-service PATH

env -i HOME=/home/agent PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
  .venv/bin/python - <<'PY'
from llm_processor import _resolve_codex_bin
print(_resolve_codex_bin())
PY

# Service and HTTP checks
systemctl --user restart autoprotocol-webapp.service
bash scripts/status-webapp.sh
```

## Reporting

Report the actual failing stage and the verified fixed path, but avoid printing secrets or raw transcripts. Mention that raw transcript preservation was added so future LLM failures do not waste the STT run.
