You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit bfc6b18
Browse filesBrowse the repository at this point in the historyBrowse files
D: name submodule content and byte-rewriting attributes in the export; check the stderr filter (#80)
* Name submodule content and byte-rewriting attributes in the export; check the stderr filter's status
The non-recursive task clone leaves a submodule as an empty directory. Git never lists files there, so an agent's
files (or a repository it commits in) vanished from the export; a submodule directory that is not empty, or cannot be
read, is now named in a notice. The built-in ident and working-tree-encoding attributes change what the diff shows
without any config, so a changed or new file with either set is named too. Line-ending attributes are left alone:
overriding them would make every CRLF checkout look changed.
The stderr filter's exit status is now checked, since a filter that failed (for example on a full /tmp) could lose a
read failure, and SIGPIPE is accepted only when the output really reached its limit. The docs now say the failure
carries Git's last two error lines, as the code does.
Findings from the PR 74 review session's round 2 on 176a631 (PR 76, already merged).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test the unreadable-submodule, filter-failure and early-SIGPIPE paths of the export
An unreadable submodule directory is now in the real-Docker submodule case. The filter failure and a SIGPIPE before
the limit need an exactly full /tmp to happen for real, so two tests run the export script with a failing stand-in for
awk or head first on PATH; EXPORT_SCRIPT is exported for them. Each test fails when its check is removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/implementation/runner-lifecycle.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -237,7 +237,7 @@ The server computes `retryable` and sends it to the UI. The UI never works it ou
237
237
238
238
| Holder | Owner | Rule |
239
239
|---|---|---|
240
-
| Partial output of a stopped writable attempt | F, using a D export | Done in D by #51 item 6: `exportTaskDiff(storage, { base, imageId, maxBytes, signal, onProcessGroup, timeoutMs })` (`agents/container/storage.ts`) takes the storage value or a recovery handle and `base`, the full ID of the last commit codeboost made there, and returns `{ diff, truncated }`: at most `maxBytes` (1 MiB at most) of raw diff bytes. It checks both volumes' labels, runs Git in a read-only container with no network that writes nothing to either volume, covers committed, staged, unstaged and new untracked files, and cuts the output inside the container, so the Docker work stops at the limit. A changed file over 8 MiB, an untracked nested repository, or an untracked path Git will not add (a part named `.git`, or a symlink named `.gitmodules`) is named in a `codeboost:` notice line instead of diffed, so one large file cannot exhaust the export's memory or deadline; untracked symlinks are diffed as links. What Git skips without a word is named in a notice line too: an entry named `.git` (and, if it holds a repository, the new files beside it), a fifo or socket, and an ignored untracked path. Anything it cannot read fails the export, and a Git failure's reason includes Git's first error line. On abort or at its deadline it stops the client and removes the export container before it settles (an `AbortError`, or an `AggregateError` if that removal did not settle); recovery removes a leftover one. The task filesystems are Docker volumes behind a keeper container. F allocates them with `prepareTaskFilesystems` and removes them with `removeTaskFilesystems`; D's supervisor does not remove them at settlement. But F cannot read a volume from the host. So the order is: `settled` → a **D-provided bounded export** (a new D operation, for example `exportTaskDiff(filesystems, maxBytes)`, which runs `git diff` against the last codeboost commit in a read-only container and returns at most 1 MiB) → F saves it → the terminal write stores `diagnostic_ref` → `removeTaskFilesystems` → the slot is freed. **The export has an overall deadline** (60 seconds, passed to D as an `AbortSignal`), because `maxBytes` bounds only the returned data, not the Docker work. On timeout or any export failure, F records "Partial output could not be exported: <reason>" as the diagnostic and continues with the terminal write and removal; shutdown and recovery never wait on an export past its deadline. F saves the export to a runner-owned diagnostics directory, with a total byte cap. **Retention never deletes a file an attempt row still references.** When the cap is exceeded, retention first deletes unreferenced files, oldest first. If that is not enough, it takes the oldest referenced file and, in one transaction, sets that row's `diagnostic_ref` to null and appends "(partial output removed by retention)" to its diagnostic. Only after that commit does it delete the file. No row is ever left pointing at a missing file. The attempt row references it as `diagnostic_ref`. If the capture fails, the row records that it failed. The task filesystem is never reused. |
240
+
| Partial output of a stopped writable attempt | F, using a D export | Done in D by #51 item 6: `exportTaskDiff(storage, { base, imageId, maxBytes, signal, onProcessGroup, timeoutMs })` (`agents/container/storage.ts`) takes the storage value or a recovery handle and `base`, the full ID of the last commit codeboost made there, and returns `{ diff, truncated }`: at most `maxBytes` (1 MiB at most) of raw diff bytes. It checks both volumes' labels, runs Git in a read-only container with no network that writes nothing to either volume, covers committed, staged, unstaged and new untracked files, and cuts the output inside the container, so the Docker work stops at the limit. A changed file over 8 MiB, an untracked nested repository, or an untracked path Git will not add (a part named `.git`, or a symlink named `.gitmodules`) is named in a `codeboost:` notice line instead of diffed, so one large file cannot exhaust the export's memory or deadline; untracked symlinks are diffed as links. What Git skips without a word is named in a notice line too: an entry named `.git` (and, if it holds a repository, the new files beside it), a fifo or socket, an ignored untracked path, and anything in a submodule directory. A changed file whose `ident` or `working-tree-encoding` attribute changes what the diff shows is named too. Anything it cannot read fails the export, and a Git failure's reason includes Git's last two error lines. On abort or at its deadline it stops the client and removes the export container before it settles (an `AbortError`, or an `AggregateError` if that removal did not settle); recovery removes a leftover one. The task filesystems are Docker volumes behind a keeper container. F allocates them with `prepareTaskFilesystems` and removes them with `removeTaskFilesystems`; D's supervisor does not remove them at settlement. But F cannot read a volume from the host. So the order is: `settled` → a **D-provided bounded export** (a new D operation, for example `exportTaskDiff(filesystems, maxBytes)`, which runs `git diff` against the last codeboost commit in a read-only container and returns at most 1 MiB) → F saves it → the terminal write stores `diagnostic_ref` → `removeTaskFilesystems` → the slot is freed. **The export has an overall deadline** (60 seconds, passed to D as an `AbortSignal`), because `maxBytes` bounds only the returned data, not the Docker work. On timeout or any export failure, F records "Partial output could not be exported: <reason>" as the diagnostic and continues with the terminal write and removal; shutdown and recovery never wait on an export past its deadline. F saves the export to a runner-owned diagnostics directory, with a total byte cap. **Retention never deletes a file an attempt row still references.** When the cap is exceeded, retention first deletes unreferenced files, oldest first. If that is not enough, it takes the oldest referenced file and, in one transaction, sets that row's `diagnostic_ref` to null and appends "(partial output removed by retention)" to its diagnostic. Only after that commit does it delete the file. No row is ever left pointing at a missing file. The attempt row references it as `diagnostic_ref`. If the capture fails, the row records that it failed. The task filesystem is never reused. |
241
241
| Interrupted rebase | F3 | F3 records `rebase in progress` durably before starting a rebase and clears it after. It is stored as `tasks.rebase_in_progress`: null, or JSON `{ attemptId, oldHead, onto, startedAt }`. F3 sets it by compare-and-swap from null, and clears it by compare-and-swap on the same `attemptId`. Startup recovery aborts every recorded rebase through the runner before it hands the task to I3. |
242
242
| Workspace rebuild | I3 | I3 never rebuilds before both rows above are resolved for the task. |
0 commit comments