# Files and commits

Read and edit files in a chat session's sandbox and read its git state. The sandbox must be running, so wake it first with `GET /api/projects/{projectId}/preview?sessionId=`. A write changes the working tree only. Ask the agent to review and include your edits in its next turn. In a Collaboration project, the agent commits and pushes its changes at the end of every turn. Review the diff, then ask the agent in the chat to open the pull request, or send `/kosuke:open-pr` (`$kosuke:open-pr` on a chat session whose Model runs on OpenAI).

| Method | Path                                                                              | Description                                                                                                        |
| ------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| GET    | `/api/projects/{projectId}/chat-sessions/{sessionId}/preview/files`               | List the files in the session's sandbox. Empty when the sandbox is not running.                                    |
| GET    | `/api/projects/{projectId}/chat-sessions/{sessionId}/preview/files/content?path=` | Read one file from the session's sandbox.                                                                          |
| PUT    | `/api/projects/{projectId}/chat-sessions/{sessionId}/preview/files/content`       | Write one file in the session's sandbox. Refused while a turn is running, and for binary content.                  |
| GET    | `/api/projects/{projectId}/chat-sessions/{sessionId}/preview/git-state`           | Read whether the session's sandbox has uncommitted changes, unpushed commits, or commits ahead of its base branch. |

## Read the file tree

`GET .../preview/files` lists the project's files in the sandbox.

```bash
curl -s "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/preview/files" \
  -H "Authorization: Bearer $KOSUKE_API_KEY"
```

```json
{
  "data": {
    "files": [
      {
        "name": "src",
        "type": "directory",
        "path": "src",
        "children": [
          { "name": "page.tsx", "type": "file", "path": "src/page.tsx", "size": 1843, "lastModified": "2026-09-30T09:14:02.511Z" }
        ]
      }
    ]
  }
}
```

Paths are relative to the project root. The tree leaves out the folders `node_modules`, `.git`, `.next`, `dist`, `build`, `coverage`, `.turbo`, `.cache`, `__pycache__`, `venv` and `.venv`, and the files `.DS_Store` and `Thumbs.db`. When the sandbox is not running, `files` is empty. The CLI command is `kosuke sessions sandbox-files list <projectId> <sessionId>`.

## Read a file

`GET .../preview/files/content` returns one file's text.

| Parameter | What it does |
| --- | --- |
| `path` | Required. The file's path, relative to the project root. |

```bash
kosuke sessions sandbox-files get $PROJECT_ID $SESSION_ID --path src/page.tsx
```

The answer is `{ "data": { "content": "..." } }`. A path that does not exist, or that leads outside the project, answers `404`.

## Write a file

`PUT .../preview/files/content` replaces the content of a file that exists. To add a new file, ask the agent in a message.

| Field | Type | What it does |
| --- | --- | --- |
| `path` | string, up to 2,048 characters | Required. The file's path, relative to the project root. |
| `content` | string, up to 1,048,576 characters | Required. The whole new content. Text only. |
| `clientId` | uuid | Required. A UUID you generate, for example with `uuidgen`. |

```bash
curl -s -X PUT "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/preview/files/content" \
  -H "Authorization: Bearer $KOSUKE_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"path\": \"README.md\", \"content\": \"# Storefront\\n\", \"clientId\": \"$(uuidgen)\"}"
```

```json
{ "data": { "path": "README.md", "updatedAt": "2026-09-30T09:31:47.220Z" } }
```

Members with the session open see the change at once. The change is not committed. A write while a turn runs answers `409` with `JOB_ACTIVE`, and binary content answers `400`. The CLI command is `kosuke sessions sandbox-files set <projectId> <sessionId> --path <path> --content <text> --client-id <uuid>`.

## Read the git state

`GET .../preview/git-state` reports the state of the working tree and branch.

```json
{
  "data": {
    "available": true,
    "hasUncommittedChanges": true,
    "hasUnpushedCommits": false,
    "hasCommitsAheadOfBase": true
  }
}
```

| Field | What it means |
| --- | --- |
| `available` | `false` when the sandbox is not running. The other fields are then `false` too. |
| `hasUncommittedChanges` | The working tree has changes nobody committed. |
| `hasUnpushedCommits` | The branch has commits that are not on GitHub yet. |
| `hasCommitsAheadOfBase` | The branch has commits its pull request's base branch does not have, or the Target Branch before a pull request is open. |

Reading the git state never wakes a sandbox. The CLI command is `kosuke sessions git state get <projectId> <sessionId>`.

## Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | A field is missing, too long, or `clientId` is not a UUID. |
| 400 | `BAD_REQUEST` | A read has no `path`, the sandbox is not running for a read or a write, the content is binary, or the write failed. The message says which. |
| 402 | `CREDIT_LIMIT_REACHED` | The workspace is blocked on billing. |
| 404 | `NOT_FOUND` | The project, session or file does not exist. |
| 409 | `JOB_ACTIVE` | A turn is running. Write again once it ends. |
| 503 | `SERVICE_UNAVAILABLE` | The sandbox could not list the files, read the file or read the git state. Retry. |

[Responses and errors](https://kosuke.ai/docs/api/errors.md) has the full table.

## Next steps

- [Diff](https://kosuke.ai/docs/endpoint-reference/diff.md)
- [Pull requests and sync](https://kosuke.ai/docs/endpoint-reference/pull-requests-and-sync.md)
- [Previews](https://kosuke.ai/docs/endpoint-reference/previews.md)
