# ADR 0218: view_file rejects a list encoded as text

> Source: https://github.com/max-listov/stitchkit/blob/master/docs/decisions/0218-view-file-rejects-a-list-encoded-as-text.md

---

# ADR 0218 — view_file rejects a list encoded as text

## Context

`paths` accepts one path as a string or a real array of paths. The string
`'["image.png"]'` passes an ordinary `z.string()`, but it means one wrong
filename. The extension check then reports a refusal to read media, although the
cause is the shape of the argument. The shared coercion deliberately keeps strings
in a string-union: an automatic JSON.parse changes the set of valid string values.

## Decision

The shared `ViewFileInputSchema` checks the semantics of each path. If a string
parses as a JSON array, the schema rejects it with the message
`paths is a list written as text — pass an array of paths or one path`.
The rule also applies inside an array, on the managed MCP/Agent/CLI surfaces and
on raw MCP. The refinement does not parse the string into an argument and does not
change the shared coercion. A correct bracket filename `[preview].PNG` stays valid.

The local refusal by extension, before the file is read, names the path the user
passed and the extension it found. Hidden paths of the file boundary stay hidden.

## Consequences and verification

Validation became stricter: release 0.102.0 marks this as a breaking change and
provides a migration from a string-encoded JSON array to a real array. Managed
surfaces return `VALIDATION_ERROR`; raw MCP uses its own input-validation refusal.

`managed-view-file.test.ts` checks both forms through real MCP/Agent mounts, the
refusal before any file/network IO, and the following successful read of one path
and of an array. A bad array element, an empty list written as text and a bracket
filename are separate counterexamples. The local refusal is checked by code, path,
extension and zero reads. The decision serves I2, I3, I8 and I13.
