Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ jobs:
- common
- gh
- ssh-agent
- cloud-folder
- az
- kube-state
baseImage:
Expand Down Expand Up @@ -73,6 +74,7 @@ jobs:
- common
- gh
- ssh-agent
- cloud-folder
- az
- kube-state
steps:
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ installing Node.js, and a one-line bootstrap that wires both into any repo.
| [`git`](src/git) | git augmented for the container: a worktree-aware wrapper that scopes `GIT_DIR` to the checkout and maps a `.git` pointer under the host's `.git-dirs` onto one shared mount, `safe.directory` and `core.hooksPath` at post-create, the global config in a volume it owns. |
| [`gh`](src/gh) | [GitHub CLI](https://cli.github.com) from GitHub releases, checksum-verified, unless the official Feature installed it; its login (`GH_CONFIG_DIR`) in a volume it owns, git's GitHub credentials taken from it. |
| [`ssh-agent`](src/ssh-agent) | `SSH_AUTH_SOCK` at a fixed path in every process, so git commits can be signed with an SSH key: the launcher mounts the host's agent socket (linux, macOS, a WSL distro), the private key never entering the container; the Windows engines get a plain explanation and an opt-in, temporary key mode; `ssh-agent:*` tasks for keys, dedicated-key warnings, rotation and retirement (`key:rotate`, `key:retire`, `key:age`) and git signing setup. |
| [`cloud-folder`](src/cloud-folder) | One fixed path (`/mnt/cloud-folder`, the `mountPoint` option) for a shared document folder that must never go in git (SharePoint, OneDrive): today host mode, the launcher bind-mounting the host folder a sync client keeps (`DCF_HOST_CLOUD_FOLDER`) at `/mnt/cloud-folder-host`, linked from `mountPoint`; `cloud-folder:status` says whether it is mounted, empty and writable and fails when it is not, so a start-up check can stop. rclone sync and mount come later. |
| [`az`](src/az) | Augments the official `azure-cli` Feature: its login (`AZURE_CONFIG_DIR`) in a volume it owns, `az bicep` using the Bicep on PATH. |
| [`kube-state`](src/kube-state) | Owns the kubeconfig (`KUBECONFIG`) the Kubernetes tools share, in a volume it owns, so velero and kubectl stop needing a hand-mounted `~/.kube`; installs no binary. |
| [`common`](src/common) | The shared helper behind every Feature's post-create hook (`dcf-persist`: make a state directory usable and say what backs it), for a repo's own scripts too. |
Expand Down Expand Up @@ -231,6 +232,7 @@ run is configured:
| Ordered, comma-separated host types `auto` tries (default per OS: Windows `windows-docker,windows-wsl-docker,windows-wslc`, inside WSL `windows-wsl-docker`, `linux`, `macos`) | `HOST_TYPE_PRIORITY` | `HOST_TYPE_PRIORITY=` var |
| `false` (or `0`, `no`): the launcher adds no host mounts to `up`. By default it adds the host home's `.git-dirs` at `/git-dirs` and, where the host type has one, the SSH agent socket at the `ssh-agent` Feature's fixed path (each skipped when the repository's `devcontainer.json` already mounts that target), so a repository declares no mount line; `task devcontainer:host` lists them. | `HOST_MOUNTS` | `HOST_MOUNTS=` var |
| `key`: opt into a signing key kept in the container (the `ssh-agent` Feature's key mode, which breaks "the private key never enters the container" for you): `DCF_HOST_SSH_AGENT=key` and no agent socket mounted. Unset: the host type decides (socket on linux, macos, a WSL distro; none on the Windows engines). | `SSH_AGENT_MODE` | `SSH_AGENT_MODE=` var |
| A host folder a sync client keeps (SharePoint, OneDrive) that `up` bind-mounts at `/mnt/cloud-folder-host`, where the `cloud-folder` Feature's `mountPoint` links; passed on unchanged for `containerEnv`. Never created: a path missing on this host is skipped with a warning. Unset: no folder. | `DCF_HOST_CLOUD_FOLDER` | `DCF_HOST_CLOUD_FOLDER=` var |
| Portable Node.js version | `DEVCONTAINER_NODE_VERSION` | - |
| `@devcontainers/cli` version (default `latest`) | `DEVCONTAINER_CLI_VERSION` | - |
| Root of the host-side tool-state folders (`devcontainer:homes`; default `~/.devcontainer-homes`) | `DEVCONTAINER_HOMES` | - |
Expand Down Expand Up @@ -292,6 +294,14 @@ signing key in the container's volume and so breaks "the key never enters the co
`task ssh-agent:key:new` makes one inside the container). Then `task ssh-agent:git:signing` and `task ssh-agent:git:verify`;
`task ssh-agent:key:rotate` replaces the key in four steps and `key:retire` / `key:age` retire it and show its age.

**A shared document folder outside git (`cloud-folder` 1.0.0, devcontainer-cli 1.12.0):** add
`ghcr.io/nabil-becker/devcontainer-features/cloud-folder:1` to the `features`, map `"DCF_HOST_CLOUD_FOLDER":
"${localEnv:DCF_HOST_CLOUD_FOLDER}"` with `containerEnv` (the bootstrap's template does), and set
`DCF_HOST_CLOUD_FOLDER=<the synced folder on this host>` in `.devcontainer/.env`. `up` then mounts it at
`/mnt/cloud-folder-host`, and the Feature's `mountPoint` (default `/mnt/cloud-folder`) links there: the path in the
container is named once, in that option. Drop a hand-written bind mount of the folder from `devcontainer.json`. Put
`task cloud-folder:status` in your start-up check: it fails when the folder is missing or unwritable.

All of them also answer to `dc:` (the include's alias in the generated
Taskfile). Each task dispatches per platform (Task's per-command
`platforms`):
Expand Down
14 changes: 13 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ stays. Ids are short descriptive slugs (two to four kebab-case words naming the
- [ ] **gh-ssh-key-format-check Check the real output of gh ssh-key list** (engineers). `REVOKE=1` finds the old key's id by parsing `gh ssh-key list`, which only the stubs have produced; run one real rotation on a throwaway key, fix the parse if the format differs, and file what it shows. Added 2026-10-24.
- [x] **fleet-reconcile-live-sessions The fleet waits for restored sessions, reports and stops duplicates** (host). Requested by a consuming repository running four agents in one container (2026-10-24): `claude-fleet` waits until the restored session list is stable before reconciling (`--no-wait` skips it), reads ids from `.devcontainer/.env` before the environment, reports every agent with more than one live session and, only with `--stop-duplicates`, stops the extras with `claude stop` (never rm); `claude-host`'s refusal names `claude attach <id>`. claude-code 1.6.0, devcontainer-cli 1.11.0. Added 2026-10-24. Done 2026-10-24 (branch `feature/fleet-reconcile-live-sessions/1`).
- [x] **release-claude-code-1-6-1 Release claude-code 1.6.1 after the pin-hook and attach fixes merge** (the Operator). 1.6.1 carries the pin-hook and attach-advice fixes found on the workstation; the host reviews the con's changes, the Operator says "commit" and opens the pull request, and merges it once validate, lint, tests and DCO pass. The host does not start the release; it says when one is due. Added 2026-10-24. Done 2026-10-24.
- [ ] **cloud-folder-build Build the cloud-folder Feature** (host). A central SharePoint folder in the container through rclone, decided 2026-10-27 (`docs/proposals/cloud-folder.md`, section 7): slice 1 (`host` mode, `status`) is in progress; slice 2 (`sync`, sign-in, the shutdown check) and slice 3 (`mount`, FUSE granted per machine) remain. The office can adopt after slice 1. Added 2026-10-27.

### Devhost and the way of working

Expand Down Expand Up @@ -262,7 +263,15 @@ stays. Ids are short descriptive slugs (two to four kebab-case words naming the
- [x] **pin-hook-env-visibility The pin hook only looks in `.devcontainer/.env`** (host). Found 2026-10-24 when a `/clear` left `HOST_SESSION` at the id from before the clear: this repository keeps the line in the root `.env` (the Taskfile's `dotenv: ['.env', '.devcontainer/.env']`), and `claude-host-pin` (hook and `--replace`) searches only `.devcontainer/.env`, so it wrote nothing ("no .devcontainer/.env above …"). Search the Taskfile's dotenv files in its order, in the hook, `--replace` and `claude-fleet`, with tests. The check that the pin follows a `/clear` is `pin-hook-first-clear`. Added 2026-10-24. Done 2026-10-24 (branch `fix/claude-host-attach-kind/1`).
- [ ] **fleet-attach-wording claude-fleet gives the right attach advice** (host). `claude-fleet` still prints `claude attach <id>` for every running session, interactive ones included, which fails for a session a terminal holds. Follow `claude-host`'s advice by session kind (`claude-host-attach-kind`), in the fleet's output and the claude-code README. Added 2026-10-24.
- [ ] **shutdown-check-records-note The shutdown check notes uncommitted records instead of refusing** (host). The check refuses every rebuild on the backs' own uncommitted ledger, backlog and cost changes and on their waiting stretches, which a rebuild keeps, so the Operator types `FORCE=1` each time and the check protects nothing. Make those a note; block only work a rebuild would lose: a back mid-write, a test run, unpushed commits, a night shift halfway through. Stale runner notes stay with `runner-notes-block-stop`. Added 2026-10-24.
- [ ] **fleet-repo-check Run the fleet on the workstation in this repository** (the Operator). The root `Taskfile.yml` now sets `SESSIONS` (uncommitted), so `task dc:claude:fleet` passes its precondition; only a dry run has checked that. Run it here with this session up (it should start nothing and open the agent view) and with none running (it should start one), and file the output if either fails; the engineer's "fleet still doesnt work here" waits on it. Added 2026-10-24.
- [x] **fleet-repo-check Run the fleet on the workstation in this repository** (the Operator). The root `Taskfile.yml` sets `SESSIONS` (PR #80), so `task dc:claude:fleet` passes its precondition; only a dry run has checked that. Run it here with this session up (it should start nothing and open the agent view) and with none running (it should start one), and file the output if either fails; the engineer's "fleet still doesnt work here" waits on it. Added 2026-10-24. Done 2026-10-26: run on the workstation with this session up, it started nothing and opened the agent view.
- [x] **release-ssh-agent-1-1-1 Release ssh-agent 1.1.1 after the git:verify fix merges** (the Operator). The fix on `fix/ssh-agent-git-verify/1` (not yet committed) ends `git:verify`'s `tmp: unbound variable` under `set -u` and the temporary folder it leaves in `/tmp`; on Windows hosts `git:signing` and key rotation now default to `SCOPE=global`. Commit and merge it, then run the release; the host does not start the release. Added 2026-10-26. Done 2026-10-26.
- [ ] **worktree-empty-folders EnterWorktree leaves folders the container cannot delete** (host). A worktree made with EnterWorktree inside the OneDrive checkout leaves empty folders the container cannot remove (`rm -rf` and `rmdir` fail with "Not a directory"); the leftover `.claude/worktrees/fix-ssh-agent-verify` is one, and the Operator deletes it from Explorer on the workstation. Make worktrees with `git worktree add` in the scratchpad, as CLAUDE.md says, and note the EnterWorktree trap there or in the subagent brief. Related: `devhost-worktree-isolation`. Added 2026-10-26.
- [ ] **duplicate-warning-gap Learn what the session-start duplicate warning does and close its gap** (host). agentic-operating-framework still shows a session-start duplicate warning from its own script, a workaround since its ssh-agent 1.1.1 report (2026-10-27). Read that script in the consuming repository, then check whether `claude-fleet`'s duplicate report (`fleet-reconcile-live-sessions`) or `claude-host`'s refusal already covers it, and close what is left here. Added 2026-10-27.
- [ ] **backs-paste-truncation Find out whether the backs truncate pasted requests** (host). The secretary reported two complete pasted requests as cut off (2026-10-27), and the host found both complete, so the back likely sees a shortened copy of pasted text. Find where the paste is cut (the job's input or the condensed stretch), then fix it there or have the back stop flagging a paste as cut off, with a test. Added 2026-10-27.
- [ ] **psscriptanalyzer-tracked-files The PowerShell lint checks tracked files only** (host). `task lint` fails on a workstation checkout because the PSScriptAnalyzer step scans the git-ignored `material.ignore/` folder and reports 32 problems in files git never sees; the earlier `lint-ignore-local-folders` fix covered yamllint only. Build the analyzer's file list from tracked and non-ignored files, as the YAML check does, with a test. Added 2026-10-27.
- [ ] **cloud-folder-ci-check Read the first CI run of the cloud-folder tests** (engineers). The Feature's container tests (`test/cloud-folder`) and the collection-wide test have not run with Docker anywhere but CI; read the first CI run and file what it shows. Added 2026-10-27.
- [ ] **cloud-folder-workstation-up Run cloud-folder once on the workstation** (the Operator). One real `up` with a cloud folder whose path has spaces, on Docker Desktop and on wslc, to check the launcher's mount and `cloud-folder:status` on real hosts. Added 2026-10-27.
- [ ] **release-cloud-folder Release cloud-folder 1.0.0 and devcontainer-cli 1.12.0 after the step 1 pull request merges** (the Operator). The host does not start the release. Until it runs, consumers cannot pull cloud-folder or name it in `installsAfter`. Added 2026-10-27.

## Next

Expand All @@ -279,6 +288,8 @@ stays. Ids are short descriptive slugs (two to four kebab-case words naming the
- [ ] **kube-state-installs-after kube-state integrations and test count derivation** (host). After release (release-collection): add kube-state to `installsAfter` in velero, kustomize, argocd and kubeconform Features; derive Feature count in test/_global from src/ listing instead of hard-coded value. (was TD-17) Added 2026-10-16.
- [x] **merge-features-pr Merge the Features PR #12** (the Operator). devhost 1.2.3 and git 1.1.0 (the src and test commits plus the sign-off hook fix), on `features/devhost-and-git-fixes`; the records PR #13 stacks on it. Merge with the admin bypass once CI is green, then run the release. Added 2026-10-16. Done 2026-10-19.
- [x] **new-feature-checklist One checklist for adding a Feature** (host). What a new Feature needs is spread over README, CONTRIBUTING, the CI matrices and `test/_global` (a hard-coded Feature count), and scenarios that call `task` need `go-task` listed; three cons rediscovered it. One list in CONTRIBUTING.md. Ledger review 2026-10-16, group 5. Added 2026-10-16. Done 2026-10-16.
- [ ] **claude-host-join-running claude-host joins a running session with CLAUDE_HOST_JOIN=1** (host). Requested by agentic-operating-framework (its ADR 0015, 2026-10-27): with `CLAUDE_HOST_JOIN=1`, claude-host attaches to a live background session in the folder (the pinned id if live, else the newest by `startedAt`) instead of refusing, with a one-line note on stderr and a duplicates line when more than one is live; nothing live keeps today's `--resume`/`--session-id`; an interactive one prints its session id, pid and terminal (`ps -o tty=`), says to switch to that terminal or Remote Control, and exits 1 starting nothing; `devcontainer:claude` passes the variable through. claude-code 1.7.0 and a devcontainer-cli minor bump. Their `scripts/test-join.sh` (fake `claude`; cases: nothing live as an ended entry, a dead pid or another folder; one background session; two live with the pinned one older; two live with the pin in neither; the duplicates line; an interactive session; an unreadable list) fits `task test:claude-unit`; they drop `scripts/join.sh` once the release ships. Linked to `fleet-attach-wording` (the same attach advice, same release) and `session-concept-drop` (the host sees no conflict; decide it first). Added 2026-10-27.
- [ ] **cloud-folder-feature A cloud folder in the container through rclone** (host). Requested by the client engagement office (2026-10-27): one fixed path in the container (`/mnt/cloud-folder`, option `mountPoint`) that agents read and write as a directory, filled from SharePoint or OneDrive through rclone, so it no longer depends on one Windows host's sync client and works on a Kubernetes cluster. The rclone binary is on containers.dev; no Feature configures a remote, signs in, mounts or syncs. Modes: `auto` (default: `mount`, else `sync`), `mount` (`rclone mount`, needs FUSE), `sync` (`rclone bisync` to a volume, on a timer, unprivileged), `host` (a host bind mount, no rclone); tasks `login`, `status`, `sync`, `unmount`. The Operator said yes (2026-10-27): a proposal in `docs/proposals/` first, then three slices (`host` plus `status`; `sync` plus `login`; `mount` with the launcher granting FUSE). Added 2026-10-27.

### Devhost and the way of working

Expand Down Expand Up @@ -393,3 +404,4 @@ stays. Ids are short descriptive slugs (two to four kebab-case words naming the
- [ ] **fleet-waiting-line Print the fleet's waiting line only while the list changes** (the Operator). The fleet now prints "waiting for restored sessions…" on every run and waits at least `FLEET_SETTLE_INTERVAL` (3 seconds) before starting anything. Recommendation: print it only while the session list is changing, a few lines of change; the host can fold that in before #76 merges. Added 2026-10-24.
- [ ] **session-concept-drop Drop the session from the domain?** (the Operator). Should agents stop being identified by session id, so the domain keeps only a name and a folder, with background agents that start with the container and interactive attach for humans? The pins, the pin hook and the stale-pin warnings would go with it. No recommendation was recorded; the host's shape is untested against Claude Code (`fleet-design-rethink`). Added 2026-10-24.
- [x] **commit-sessions-setting Commit the fleet's SESSIONS setting on a topic branch** (the Operator). The `SESSIONS` line in the root `Taskfile.yml` is uncommitted on a detached HEAD beside the backs' records changes; committing needs a topic branch first (`task devhost:branch`). Commit it on a branch, or leave it uncommitted? Added 2026-10-24. Done 2026-10-24: "yes", committed on a topic branch.
- [ ] **cloud-folder-commit Commit, push and open the pull request for cloud-folder step 1** (the Operator). Step 1 (`host` mode), the proposal, ADR 0031 and the records are in the working tree, nothing committed; the host recommends one commit, with cloud-folder at 1.0.0 and devcontainer-cli at 1.12.0, once the Operator has checked the rework. Added 2026-10-27.
Loading
Loading