Skip to content

Commit af0dd86

Browse files
committed
docs: document the fs:write endpoint and stop tracking the nix build symlink
The filesystem section listed every fs action except write, and 40928 was missing from the conflict row, so a client had no way to learn that base_etag is what makes a save safe against a concurrent change. result is a nix build output symlink that was committed by accident; it is now ignored.
1 parent 7db4f89 commit af0dd86

5 files changed

Lines changed: 34 additions & 3 deletions

File tree

.changeset/fs-write-endpoint.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": minor
3+
---
4+
5+
Add `POST /api/v1/sessions/{id}/fs:write` so API clients can save workspace files; passing `base_etag` fails with `40928` instead of overwriting a concurrent change.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": minor
3+
---
4+
5+
web: Open and edit workspace files in the browser, with a save that refuses to overwrite a change made elsewhere since you opened the file.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,4 @@ HANDOFF*.md
5555
handoff.md
5656
handover.md
5757
.tmp-dev.log
58+
result

docs/reference/server-api.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Error codes are grouped by band:
6565
| `400xx` | Bad request | `40001` validation failed (`details` lists each field), `40003` provider is OAuth-managed |
6666
| `401xx` | Auth and readiness | `40101` unauthorized, `40110` no provider configured, `40113` model not resolved |
6767
| `404xx` | Not found | `40401` session, `40408` MCP server, `40409` file path |
68-
| `409xx` | State conflict | `40901` session busy, `40902` approval already resolved, `40922` page conditions mismatch `page_token` |
68+
| `409xx` | State conflict | `40901` session busy, `40902` approval already resolved, `40922` page conditions mismatch `page_token`, `40928` `fs:write` `base_etag` is stale |
6969
| `410xx` | Expired | `41001` approval timed out, `41002` question timed out, `41003` temporary file expired |
7070
| `413xx` | Size or boundary exceeded | `41302` file read over 10 MB, `41304` path escapes the session directory |
7171
| `429xx` | Rate limited | `42901` auth-failure ban, `42902` too many fs watches |
@@ -219,7 +219,7 @@ PTY terminal endpoints; mounted only on loopback binds.
219219

220220
### File system
221221

222-
In-session file operations go through `POST /api/v1/sessions/{session_id}/fs:{action}` with JSON bodies; actions are `list` / `read` / `list_many` / `stat` / `stat_many` / `mkdir` / `search` / `grep` / `git_status` / `diff` / `open` / `open-in` / `reveal`. In addition:
222+
In-session file operations go through `POST /api/v1/sessions/{session_id}/fs:{action}` with JSON bodies; actions are `list` / `read` / `list_many` / `stat` / `stat_many` / `mkdir` / `write` / `search` / `grep` / `git_status` / `diff` / `open` / `open-in` / `reveal`. In addition:
223223

224224
| Method and path | Description |
225225
| --- | --- |
@@ -230,6 +230,27 @@ In-session file operations go through `POST /api/v1/sessions/{session_id}/fs:{ac
230230
| `GET /api/v1/fs:content` | Raw bytes of any host file (gated only by the token — be careful when exposing the port) |
231231
| `POST /api/v1/fs:mkdir` | Create a directory by absolute path |
232232

233+
#### Writing a file
234+
235+
`POST /api/v1/sessions/{session_id}/fs:write` saves a workspace file. It resolves
236+
and checks the path exactly as `fs:read` does, so it cannot escape the session
237+
directory or follow a symlink out of it, and it rejects a body over 10 MiB.
238+
239+
| Field | Description |
240+
| --- | --- |
241+
| `path` | Session-relative path to write |
242+
| `content` | File contents, encoded per `encoding` |
243+
| `encoding` | `utf-8` (default) or `base64` |
244+
| `base_etag` | The etag the edit was based on; omit to write unconditionally |
245+
246+
The response carries `{ path, size, etag, created }``etag` is the value to send
247+
as the next write's `base_etag`, and `created` distinguishes a new file from an
248+
overwrite.
249+
250+
Passing `base_etag` makes the write optimistically concurrent: if the file changed
251+
since that etag, the write is refused with `40928` (`FS_CONFLICT`) instead of
252+
overwriting the other change. Re-read the file, rebase the edit, and retry.
253+
233254
### File uploads
234255

235256
| Method and path | Description |

result

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)