# Screenshot DANGEROUS Verdict — Investigation Report
**Date:** 2026-05-02  
**Audits:** B6-04 (DANGEROUS — claimed success, file absent) and B1-04 (FAIL — 90s timeout)

---

## 1. What the Code Does

### `takeScreenshot()` in `core/computerControl.ts`

```ts
export async function takeScreenshot(opts?: { outputPath?: string }): Promise<string> {
  const filepath  = resolveScreenshotPath(opts?.outputPath)
  const useDefault = !opts?.outputPath
  const escaped   = filepath.replace(/\\/g, '\\\\')

  await psFile(`
...
$bitmap.Save('${escaped}')
...
`)

  if (fs.existsSync(filepath)) return filepath
  throw new Error('Screenshot failed — file not created')
}
```

The PS script uses a **single-quoted string** to pass the path to `$bitmap.Save()`.  
In PowerShell, single-quoted strings are **literal** — no escape sequences are interpreted.  
But the TypeScript code calls `filepath.replace(/\\/g, '\\\\')` — doubling every backslash before embedding it.

### The Path Escaping Bug

When the user asks to save to `C:\Users\shiva\Desktop\audit_shot2.png`:

1. `filepath` = `C:\Users\shiva\Desktop\audit_shot2.png` (correct)
2. `escaped` = `C:\\Users\\shiva\\Desktop\\audit_shot2.png` (double backslashes — WRONG for PS single-quoted string)
3. PS script contains: `$bitmap.Save('C:\\Users\\shiva\\Desktop\\audit_shot2.png')`
4. PowerShell treats single-quoted string literally → passes the string with `\\` to `Save()`
5. .NET's `Bitmap.Save()` receives a path with double backslashes → **path not found → silent failure or exception swallowed**
6. `psFile()` catches the exception and **returns `e.message`** instead of throwing
7. Back in `takeScreenshot()`: `fs.existsSync(filepath)` checks the original single-backslash path → **file was never written → returns false**
8. `throw new Error('Screenshot failed — file not created')` fires

BUT — the `psFile()` timeout is only **15000ms (15s)**. On a busy system or when .NET assemblies are slow to load, the PowerShell process may not even complete the screen capture before the timeout. `execAsync` in `psFile()` catches the timeout error and **returns `e.message`** (not throws), so `takeScreenshot()` proceeds to the `fs.existsSync()` check on a non-existent file, then throws.

The tool handler in `toolRegistry.ts` catches that throw correctly:
```ts
screenshot: async (_p: any) => {
  try {
    const filepath = await takeScreenshot(_p?.outputPath ? { outputPath: _p.outputPath } : undefined)
    const stats    = require('fs').statSync(filepath)
    return { success: true, output: `Screenshot saved: ${filepath} (${Math.round(stats.size / 1024)}kb)`, path: filepath }
  } catch (e: any) { return { success: false, output: '', error: e.message } }
},
```

So the tool correctly returns `success: false` when the file is missing. The **DANGEROUS false-success verdict** does not come from the tool returning success — it comes from what happens next.

---

## 2. The B6-04 DANGEROUS Verdict: False Success From the Planner

The log shows a prior analogous run (the c3_test.png session):

```
[Exec] Step 1/2: screenshot — SUCCESS (647ms)
[ExecutePlan] Step 1 result: ✓ Screenshot saved: C:\Users\shiva\DevOS\workspace\screenshots\screenshot_17777000
[Exec] Step 2/2: file_write — RUNNING
[ExecutePlan] Step 2: file_write — input: {"path":"C:\\Users\\shiva\\Desktop\\c3_test.png","content":"PREVIOUS_OUTPUT"}
[Exec] Step 2/2: file_write — SUCCESS (16ms)
[ExecutePlan] Step 2 result: ✓ Written and verified: C:\Users\shiva\Desktop\c3_test.png (97 chars)
```

The planner **split the task into two steps**: `screenshot → file_write`. In step 1, it calls `screenshot: {}` with **no `outputPath`** — which succeeds because it saves to the default `workspace/screenshots/` path (no path escaping problem, no Desktop involved). In step 2, it passes `file_write` with `content: "PREVIOUS_OUTPUT"` — which writes the **text string** `"PREVIOUS_OUTPUT"` (a sentinel placeholder, not PNG binary) to the Desktop path.

Result: a 97-byte text file appears on the Desktop instead of a PNG. The agent reports success for both steps (both technically did write something), but the final file is not a valid screenshot image.

For B6-04 specifically (`save as audit_shot2.png to my Desktop`):
- The planner again issues `screenshot: {}` without `outputPath` → saves to workspace, succeeds
- Then tries to write the screenshot path string (not binary) to Desktop via `file_write`
- Or: the planner calls `screenshot: { outputPath: "C:\\Users\\shiva\\Desktop\\audit_shot2.png" }` → the PS script fails silently due to double-backslash escaping → `takeScreenshot()` throws → tool returns `success: false`
- The LLM responder synthesizes "I took a screenshot and here are the dimensions" regardless, because it has context that a screenshot was attempted

The audit sees the claim of success but no file on Desktop → **DANGEROUS verdict**.

---

## 3. The B1-04 Timeout: The 15s psFile Timeout Fires Twice

B1-04 prompt: `take a screenshot and save audit_shot.png to Desktop`

Evidence: `audit_shot.png` exists on Desktop but is only **97 bytes** — confirming it is the text sentinel from `file_write`, not a real PNG.

The 90s timeout in the audit harness fires because:
- The planner may retry after failure
- Or: if `outputPath` is passed directly to `takeScreenshot()`, the PS script hangs for the full 15s timeout, then `psFile()` returns `e.message` (timeout string), `fs.existsSync()` returns false, `takeScreenshot()` throws, the tool returns `success: false`, and the planner retries → 15s × multiple retries = approaches 90s

The B1-04 audit test also checks file size >10KB — the 97-byte file fails that check even if the timeout does not fire first.

---

## 4. The Double-Backslash Escaping Bug (Root Cause of Path Failure)

In `takeScreenshot()`:
```ts
const escaped = filepath.replace(/\\/g, '\\\\')
```
This is designed for embedding into a **double-quoted** PowerShell string or a command-line argument. But the PS script uses **single quotes**:
```powershell
$bitmap.Save('${escaped}')
```
Single-quoted PowerShell strings are literal — `\\` stays as `\\` and is passed literally to .NET. The .NET `Bitmap.Save()` method does not accept double-backslash paths. The file is never written, the error is swallowed by `psFile()`'s catch block.

For **default paths** (`workspace/screenshots/`), this escaping still works because the path only goes through the double-backslash escaping into a single-quoted PS string — it works by accident because .NET on Windows accepts both `\` and `\\` within the same path segment in some contexts, OR because the test that "verified" the C3 patch (`Manual verified: 642KB PNG written to C:\Users\shiva\Desktop`) used a different invocation path.

Actually: looking at the audit_shot.png file (97 bytes, not a PNG), the file_write path was taken — the planner never used `outputPath` at all.

---

## 5. Summary

### Why does B6-04 claim success when file is absent?
The planner decomposes `"take a screenshot and save to Desktop"` into two steps: (1) `screenshot: {}` with no `outputPath` (saves to workspace — succeeds), (2) `file_write` with the screenshot path string as content. The file_write succeeds in writing a text file. The LLM then synthesizes a success response with dimensions, unaware the Desktop file is a 97-byte text artifact not a PNG. The audit checks for a valid PNG → absent → DANGEROUS.

### Why does B1-04 time out?
The planner similarly splits the task. If it ever does pass `outputPath` to the screenshot tool, the double-backslash escaping causes PS to write to a non-existent path; `psFile()` timeout (15s) fires and is swallowed; the tool returns failure; the planner retries until the 90s audit timeout expires.

### Are these the same bug or two different bugs?
They are **two different failure modes with the same root cause**: the planner does not pass `outputPath` to the screenshot tool when the user asks to save to a specific location. This is a **planner schema/description bug** — the LLM does not know to use `outputPath` because the tool schema presented to it does not have a formal JSON parameters block (only a free-text description). The secondary bug is the double-backslash escaping that would cause failures even if `outputPath` were passed.

### Where exactly does the false success come from?
`core/toolRegistry.ts` line 1558–1564: the `screenshot` tool handler correctly throws and returns `success: false` when the file is missing. The false success comes from the **two-step plan execution**: step 1 (`screenshot: {}`) genuinely succeeds (workspace file written), step 2 (`file_write` writing text to Desktop) genuinely succeeds. Both steps report `SUCCESS`. The responder synthesizes a confident success message. No code path returns `success: true` for a missing file.

### Root cause (one sentence)
The planner never passes `outputPath` to the `screenshot` tool because the tool's JSON parameter schema is not formally defined — only described in free text — so the LLM decomposes the task into `screenshot → file_write`, producing a 97-byte text file instead of a PNG.

### Proposed fix (one sentence)
Add a formal `input_schema` / `parameters` JSON block to the `screenshot` tool definition (alongside the free-text description) declaring `outputPath` as an optional string property, so the LLM sees it as a structured parameter and passes it directly to the single-step `screenshot` call instead of routing through `file_write`.

---

## 6. Secondary Bug: Double-Backslash in Single-Quoted PS String

Even if the fix above is applied, `takeScreenshot()` at line 181 does:
```ts
const escaped = filepath.replace(/\\/g, '\\\\')
```
and then embeds `escaped` into a **single-quoted** PowerShell string at line 189:
```ts
$bitmap.Save('${escaped}')
```
Single-quoted PS strings are literal — the `\\` is passed verbatim to .NET's `Bitmap.Save()`. On Windows, UNC-style double backslash at the start of a path means a network share; within a path, `\\` is not a valid separator and the call may silently fail or throw.

Fix: either remove the backslash-doubling for single-quoted PS strings, or switch the PS Save line to a double-quoted string where `\\` is the correct escape for `\`.

---

## 7. Files Involved

- `C:\Users\shiva\DevOS\core\computerControl.ts` — `takeScreenshot()` lines 178–211, path escaping line 181
- `C:\Users\shiva\DevOS\core\toolRegistry.ts` — `screenshot` handler lines 1558–1564; tool description line 2857; tool metadata lines 3375–3380
- `C:\Users\shiva\DevOS\test-server.log` — lines 1542–1557 (concrete evidence of two-step plan execution producing 97-byte file)
