This file provides guidance to AI assistants (Claude, Gemini, Copilot, OpenCode etc.) when working with code in this repository.
devbox provisions a containerised remote development environment on the AI coding workstation: one
Docker container running an unprivileged sshd published only on the node's Tailscale address, so a herdr
client can attach to it as a saved machine and run OMP and Claude Code agents inside it. The container is the agent
sandbox -
it reaches the project tree, the internet and a rootless project Docker daemon, never the host filesystem or
the host's root Docker daemon.
- Host: Ubuntu 26.04 LTS, Docker with compose v2, Tailscale >= 1.98; repo lives at
~/devbox - Image:
ubuntu:26.04+ pinnedherdr,gh,lazygit,wt(worktrunk),terraform, the Docker CLI with the compose and buildx plugins, and Node 24 / pnpm 12 through nvm in/opt/nvm. Noopand nogcloud: the container holds no vault and no Google account (seedocs/secrets.md) - Agents: OMP and Claude Code, installed by bootstrap into
~/.local/binon the bind mount (unpinned, likemoshi-hook: each updates itself there), both started through the agent launchers - Runtime:
sshdon container port 2222, published as${BIND_ADDR}:2223and127.0.0.1:2223 - Project Docker: a second, rootless
dockerdowned by the dedicated host userdev(uid 1001), socket/run/devbox/docker.sockbind-mounted in; provisioned once bysudo ./bin/devbox docker setup - Config:
.env(from.env.example),docker-compose.yml,container/*,home/*
- Package manager -
apt-getonly in scripts and Dockerfiles (neverapt):apthas no stable CLI interface and warns on every scripted call bin/devboxis generated by bashly fromcli/- editcli/bashly.yml,cli/commands/andcli/lib/, runmake buildfrom the repo root and commit both (bashly formats it withshfmt -i 2, the repo's shell style;make checkfails on a stale script); never hand-editbin/devbox(the workstation has no Ruby, which is why the generated script is committed). Workstation and laptop share this one CLI, each command side-guarded by a bashlyfilters:entry; the laptop needs bash >= 4.2 (macOS/bin/bashis 3.2, sobrew install bash). Bodies keeplog_info/log_success/log_warn/log_errorfromcli/lib/log.sh;container/scripts stay hand-written bash withset -euo pipefail. Read a flag whose name has an inner dash with a quoted key,${args['--allow-unguarded']}: shfmt reads an unquoted subscript as arithmetic and rewrites it to--allow - unguarded, a key bashly never sets- Pinned versions only - every external binary comes from an explicit
ARG <TOOL>_VERSIONand is verified: against upstream's checksum file where it publishes one, else against the SHA-256 GitHub records for the release asset (its release API'sdigest), else through a signed package repository whose key fingerprint is pinned (the Docker CLI). The one exception is nvm's installer, pinned by tag over TLS only (upstream publishes no checksum file or asset digest for it); nvm then checks Node against nodejs.org'sSHASUMS256.txt. Never invent a hash - No root in the container - no
privileged, nocap_add, no/var/run/docker.sockmount.sshdruns asdev. Project containers come from the rootless sibling daemon, never from the host's root daemon and never from a nested one: nesting needs setuidnewuidmap, whichcap_drop: ALLplusno-new-privilegesdeliberately make impossible (docs/docker.md) BIND_ADDRis the security boundary - never publish a port without it, never add0.0.0.0bindings- Bootstrap stays idempotent - every step in
container/bootstrap.shis guarded so a re-run is a no-op - Commits - Conventional Commits v1.0.0, lowercase, no final punctuation, 100 chars max
- No private keys in the container - identities are public keys only; agent git goes over HTTPS through the launcher; manual work forwards the 1Password agent
-
README.md- minimal project overview, highlights, quick start, ownership; links todocs/ -
docs/README.md- documentation index; one domain per file indocs/, each ending in an FAQ (follow its conventions - page shape, emoji headings, Prettier at 120 columns - when adding or editing docs). Update the file that owns the domain rather than growingREADME.md:docs/architecture.md- system map, components, container startup order, ways in, repository layoutdocs/installation.md- prerequisites, laptop key,~/.ssh/config, first deploy, laptop agent install,.envreferencedocs/connecting.md- herdr panes,ssh devbox, Moshi on a phone,./bin/devbox shell, cloning, port forwardingdocs/git.md- the identity registry, manual vs agent git, the credential helper, signing, laptop installdocs/toolchain.md- pinned versions, install locations, OMP, Claude Code, Moshi/moshi-hook, agent skills, adding a tooldocs/secrets.md- the three secret layers,secrets.env,GH_TOKEN, the Claude Code login, GCP ADC, App credentialsdocs/cli.md- thebin/devboxreference (cheat sheet, workstation, laptop anddoctorcommands),devbox-identities, and the maintainer workflow for editing the CLIdocs/networking.md- exposure model, why UFW cannot block a published port, tunnelsdocs/docker.md- the rootless project daemon, path identity, reaching services,devbox-portsdocs/operations.md- redeploy, persistence, backup, health, troubleshootingdocs/security.md- boundaries, trust assumptions, deliberate limitsdocs/development.md- toolchain, rules, shipping a change, agent assets
-
.env.example- the only per-host configuration;.envis gitignored and never synced bydevbox deploy. Holds no identity: who this box is, per account and per directory tree, lives in~/.config/devbox/identities.confinstead (see thehome/bullet below) - an enumeration of per-account variables is what that file replaced -
Dockerfile- pinned toolchain;NVM_DIR=/opt/nvmandCOREPACK_HOME=/opt/corepackexist because/home/devis bind-mounted and would shadow a home-directory install;herdrmust land in/usr/local/binbecause non-interactive SSH sessions get the default PATH -
docker-compose.yml-${BIND_ADDR}:${DEVBOX_SSH_PORT}:2222is the entire network boundary, written${BIND_ADDR:?...}so compose itself refuses an empty address instead of publishing on0.0.0.0;user:,cap_drop: [ALL],no-new-privileges, no root docker socket, no identity inenvironment:either - same reason as.env.example.${DEVBOX_DOCKER_SOCKET_DIR:-/run/devbox}mounts the directory because rootlesskit recreates the socket inode on every daemon restart, andnetwork_mode: bridgekeeps the container ondocker0, the one interface the project-port boundary admits, and which - unlike a compose-managed bridge - is not removed bydown -
container/entrypoint.sh- PID 1 asdev: home skeleton, host key,authorized_keys, bootstrap, the project-port mirror, themoshi-hookdaemon, thenexec sshd. The order is load-bearing; the mirror comes last of the setup steps because forwards live in this container's netns and are lost on every recreate, and both it and the hook daemon are best-effort because neither an unreachable project daemon nor a missing hook daemon may cost SSH access.moshi-hook serveis backgrounded rather than supervised because there is no systemd here: it becomes a child ofsshdand is reaped by tini -
container/bootstrap.sh- idempotent user setup, sixteen individually-guarded sections: 1 the agents (OMP straight from its latest GitHub release, checked against GitHub's asset digest before it is renamed into place - upstream'somp.shinstaller checks nothing - with a download that aborts only when stalled, never on a total time cap; every run, installed or not, first deletes~/.local/bin/.omp.??????temp files older than 5 minutes, which only a killed run leaves (a live download writes at least every ~40 s), so a concurrent bootstrap's download survives; plus Claude Code via its native installer, which verifies its own download; both non-fatal, and Claude's one-time/loginregistered as a manual step until~/.claude/.credentials.jsonexists); 2 the identity registry (installsdevbox-identitiesin~/.local/libexecand symlinks it into~/.local/bin, sincedevbox.shputs only the latter on the PATH and every checklist tells people to rundevbox-identities check, thendi_checks~/.config/devbox/identities.conf- which it deliberately does not seed from the example, because the example is valid and would silently become the box's git identity - a broken registry skips every section derived from it as one block, so it costs configuration, never SSH access); 3 SSH identity public keys per identity (no prints its fingerprint to revoke); 4~/.ssh/config(rendered from the registry: one plainHost <host>block per forge, plus aMatch host <host> tagged <slug>block per identity whose key differs from the plain one); 5known_hosts(seeded per forge host); 6~/.gitconfig(the default identity's name, email and signing key); 7 your identity per tree and per GitHub owner (user-<slug>.gitconfigviaincludeIf gitdir:for every identity claiming adir,org-<slug>.gitconfigviaincludeIf hasconfig:remote.*.url:for every identity claimingorgs- written after thegitdir:includes so the owner wins over the tree - both include lists rewritten from scratch every run so a renamed or dropped identity leaves no staleincludeIf); 8allowed_signers; 9 GitHub App credential directories per identity (created, never fetched - only placed by hand); 10 shell; 11 the box-widesecrets.env; 12gh(the~/.local/libexec/devbox-agentshim plus the per-identity token checklist, each identity checked with its owndevbox-gh-token --identity <slug>andGH_HOSTset to that identity's host, for every identity); 13 the agent git override (agent-launchand both launchers plus theiromp/claudesymlinks, credential helper, fence, the renderedagent.gitconfigand oneagent-<slug>.gitconfigper identity claiming adir- every one of them, inherited authors included, since git applies every matchingincludeIfand a nested tree would otherwise keep the outer author; the includes are emitted shortestdirfirst so the longest match is read last and wins); 14 OMP config (seeded if absent with the preset the laptop shares - model roles, feature flags,secretsand thebash.patternsguardrail - an existing file lacking abash:block reported, never merged into); 15moshi-hookplus its OMP extension and Claude Code hooks (--target omp,claude); 16 the printed manual checklist -
container/sshd_config- unprivileged sshd:UsePAM no, pubkey-only, absolute paths,AllowTcpForwarding yes(dev-server tunnels),AllowAgentForwarding yes(thessh -A devboxescape hatch only - the container holds no private key of its own) andMaxSessions 32(herdr channels) -
container/skills.sh- optional, explicitly invoked (./bin/devbox skills): pinnedagent-browserCLI + Chrome build, thenagent-browser,skill-creatorandfind-skillsvianpx skills add <github tree URL at a pinned ref> --global --agent universal claude-code --yes- each skill pinned like a binary (agent-browser and find-skills at the tag of the CLI version already pinned there, skill-creator at a commit) -~/.agents/skillsfor OMP, a symlink per skill in~/.claude/skillsfor Claude Code. Chrome's shared libraries are in theDockerfilebecause--with-depsneeds root. Global npm installs pass--prefix "$HOME/.local"per call so the bins stay on the bind mount; never exportNPM_CONFIG_PREFIX- nvm then refuses to activate its default Node -
home/- templates installed into/home/devby bootstrap (and onto the laptop bydevbox agent install); generated files, not user-edited.home/.config/devbox/identities.conf.exampleis the template for~/.config/devbox/identities.conf- the one file naming who this box is, per account and per directory tree. Neither installer copies it: it validates, so seeding it would hand git and every agent session the placeholder identity instead of failing loudly.home/.local/libexec/devbox-identitiesis its one reader, used bycontainer/bootstrap.sh, the launchers and the CLI (di_slugs,di_get,di_for,di_check, thedi_render_*functions) and installed as thedevbox-identitiesCLI; deliberately bash 3.2 compatible, since the laptop-side launchers and shims run under whatever/usr/bin/env bashmacOS provides.home/.local/libexec/devbox-agent/holdsagent-launch, the one body both launchers source (omp-launcher,claude-launcherare three lines each): it exportsGIT_CONFIG_GLOBAL, the SSH fence,GIT_TERMINAL_PROMPT=0and a login-lessGH_CONFIG_DIRfor its own process tree only - on the laptop, bareghwould otherwise fall back to your OAuth login - and clears what the calling shell carries for you: an IDE terminal's askpass (GIT_ASKPASS,SSH_ASKPASS,VSCODE_GIT_*- it answers git with your own GitHub login),GITHUB_TOKENand the two enterprise token variables,SSH_AUTH_SOCK, and theGIT_AUTHOR_*/GIT_CONFIG_COUNT/GIT_CONFIG_PARAMETERSoverrides that outrank every gitconfig (GH_TOKENstays: the shim honours an explicit one as deliberate). Each launcher is reached asomp/claudeonly through a symlink and never as a file named after its tool, becauseomp updateresolves its install target by lookingompup on the PATH and takes a plain file there over in place - it once wrote the release binary onto the launcher, dropping agent sessions back on the user's gitconfig and SSH keys; the launchers drop their own PATH entries forupdate(the exports still apply, so a misread argv stays fenced) so the updater lands on the real install, and the symlink confines a missed argv shape to omp's shebang refusal. Never put a launcher symlink in~/.local/bin: Claude's native install owns~/.local/bin/claudeand re-points it on every auto-update - on the devbox the symlinks live indevbox-agentitself, on the laptop indevbox-agent/launchers, a directory holding nothing else that the user puts first on the PATH. Beside them, theghshim that deliberately shadows the realghon the PATH: withdevbox-gh-tokenit resolvesGH_TOKEN_<SLUG>per invocation from the working directory, on the samedirprefixes inidentities.confthat git'sincludeIfuses, because an agent's cwd is a project while its shell was opened in$HOME, and exportsGH_HOST=<host>with it only for an identity offgithub.comand only whenGH_HOSTis unset (gh refuses a host it holds no login for otherwise). In an agent session (GIT_CONFIG_GLOBALisagent.gitconfig), a command about one repository -pr,issue,run, ...,api repos/<o>/<r>/...,api graphqlinside a checkout- takes that repository's App installation token from
devbox-git-credential tokeninstead, so agent PRs carry the bot author their commits do; account-wide commands and every non-agentghkeep the PAT, and a failed App lookup or mint is an error, never a PAT fallback (tokenexits 1 and the shim stops; a failinggetalso answers gitquit=1, so git asks no askpass program next). The helper caches App answers per repository on tmpfs (devbox-agent-<uid>/, never under$HOME) because a mint is two API round trips; a failure is never cached. Nothing exportsGH_TOKEN;gh auth loginis rejected by design (docs/secrets.md).home/.config/devbox/git/agent.gitconfig.tplis the templatedevbox-identities render agent-gitconfigfills in with the registry's hosts and URL rewrites - edit it here, never the rendered~/.config/devbox/git/agent.gitconfigor the oneagent-<slug>.gitconfigper identity claiming adirthat it produces. Sets the bot author, unsigned commits, the HTTPS credential-helper rewrite and no prompts (credential.interactive = false, an emptycore.askPass) for agent git (docs/git.md); both installers leave that directory at mode 500 with 444 files, becauseGIT_CONFIG_GLOBALpoints into it and agit config --globalinside a session therefore rewrites the agent's own identity -~/.extradid exactly that from every login bash, putting the user's name and email on five agent commits, and git's lock file makes the directory mode the only thing that stops such a write.home/.bash_profileexists only to reassert thePATHorder for login shells: bash prefers it over~/.profile, which it sources first, because the distro's file prepends~/.local/binafter~/.bashrcand so put the realomp/claudeahead of the launchers in every interactivessh devbox, herdr pane and./bin/devbox shell;devbox.shtherefore asserts the order (move to front) instead of prepending only when absent
- takes that repository's App installation token from
-
container/devbox-ports- symlinked to/usr/local/binby theDockerfile, so a host edit is live without a rebuild; mirrors published project ports onto the container's own127.0.0.1 -
bashly-settings.yml,cli/andbin/devbox- the one CLI for both machines.bashly-settings.ymlpoints bashly atcli/and writesbin/;cli/bashly.ymldeclares the commands,cli/commands/holds their bodies,cli/lib/the shared functions,cli/initialize.shthe shared constants;bin/devboxis the generated, committed result. Commands by group:- Workstation (Linux host outside a container only):
env,up,down,rebuild,bootstrap,skills,shell,sessions,logs,hook,keys,docker setup - Laptop (macOS only):
deploy,sync omp,sync identities,agent install - Both:
install,doctor(hostorlaptop, defaulting to the side the machine is),completions [bash|zsh](the[host]ofdeploy/synccompletes from~/.ssh/config)
What the commands carry:
devbox install- thedotmodel from the dotfiles: symlinks~/.local/bin/devboxonto this checkout'sbin/devbox(never over a real file) and writes the bash completion to~/.local/share/bash-completion/completions/devbox, which bash-completion lazy-loads - no rc line. Thendevbox_command_checks(cli/lib/devbox_command.sh, also run by both doctors) probes a fresh login shell underenv -i, because the calling one proves nothing:deployrunsinstallover a non-interactivessh.cli/initialize.shresolvesREPO_ROOTthroughreadlink -ffor the symlinkup/down/rebuildrefuse to drop live SSH sessions without--force;hookrestarts themoshi-hookdaemon with a detachedexecprecisely so it does not have to (--updaterunsmoshi-hook updateand rewrites the OMP extension and Claude hooks first, aborting before the old daemon is stopped if the update fails);keysprints every identity's installed public keys (or "not set") plus the sshd host-key fingerprint - nothing to paste anywhere, since they are already the laptop's own keysdevbox docker setup- needssudo, idempotent,--checkreports only: installsuidmapandslirp4netns, creates thedev:devboxhost user with pinned uid/gid 1001 and denies it in the host's sshd (DenyUsers devin/etc/ssh/sshd_config.d/devbox-docker.conf: its~/.sshis the bind mount;sshd -truns before the drop-in is written, so a pre-existing failure is not blamed on it, and again before the reload, a failure there removing the drop-in; either shows sshd's own stderr and fails that step without aborting the rest of setup; capturedsshd -Toutput confirms it applies), movesDEVBOX_DATA_DIRto/home/dev(path identity), writes/etc/tmpfiles.d/devbox-docker.conf, installs the nftables table plusdevbox-docker-firewall.service(ordered before the host'sdocker.service, which starts the devbox, and with noExecStop- a restart'snft -fswaps the table atomically) that keeps published project ports off every interface but loopback anddocker0(--ipcovers only the default bridge, so the unit also passes--default-network-optand the table backs both up) and the host's own services out of the devbox's reach (fromdocker0only the daemon's sockets answer; host processes running as dev's uid or one of its/etc/subuidsubordinate uids - the daemon, rootlesskit, slirp4netns, anything dev's user manager starts and every--network hostproject container, which shares the host's network namespace and runs a non-root user as a subordinate uid - open nothing on the host but systemd-resolved's stub,127.0.0.53/127.0.0.54port 53, loopback included, because a project container can drive that user manager, below; the set is rendered from/etc/subuidat run time,{ 1001, 165536-231071 }by default) and both off the Tailnet (no new connection fromdocker0or those uids throughtailscale0or to100.64.0.0/10/fd7a:115c:a1e0::/48, evaluated after Docker's DNAT so a root-daemon port on the host's Tailscale address still works). That covers direct connections to the overlay only: tailscaled's LocalAPI socket is world-accessible and dials Tailnet peers for any local caller, so a project container that bind-mounts/run/tailscalerelays through it, and peers stay reachable at their LAN or public addresses (accepted limits,docs/security.md); before 1.98 tailscaled relayed a LocalAPI dial to any address as root, the host's own services included, sodoctor hostfails when the running tailscaled (tailscale version --daemon, falling back to the CLI'stailscale version) is below 1.98.doctor hostalso fails until the loaded file matches the ruleset the checkout writes and the installed unit matches the onenetfilter_unitwrites. Setup also adds the oneufwrule that lets the devbox bridge reach the gateway, and runs a lingering rootlessdockerdon/run/devbox/docker.sockfrom a root-owned unit in/etc/systemd/user, so nothing in the bind mount can rewrite the daemon's command line - plus auser@1001.servicedrop-in pointing dev'sXDG_CONFIG_HOME/XDG_DATA_HOMEat root-owned/etc/devbox-docker, since the user manager would otherwise read units, drop-ins, wants links andenvironment.dfrom the bind mount (the unit therefore passes--data-rootitself, and root makes the wants linksystemctl --user enableno longer can). The wants link and the drop-in (thendaemon-reload) are written bystep_manager, right after the data dir moves and before the firewall and netfilter steps, so this root-owned manager configuration exists before anything -loginctl enable-lingeror the firewall unit'sWants=user@1001.service- can startuser@1001, and a first provisioning's manager never reads the bind mount;user@1001is restarted only when the drop-in changed or the running manager's environment lacksXDG_CONFIG_HOME=/etc/devbox-docker/config, which restarts the project daemon and every project container (Ubuntu'sTimeoutStopSec=5SIGKILLs slow ones; one without a restart policy stays down untildocker compose up -d) - re-runs are no-ops once current, anddoctor hostcompares the manager'sExecMainStartTimestampwith the drop-in's mtime. The relocation closes only the path the devbox writes directly: through/run/user/1001(the bus,systemd/private) a project container can still change the daemon's environment or start units as dev in the host network namespace - the rules above on dev's uid and its subordinate uids are what bound such code. Old unit files in the bind mount are removed as dev, never by a rootrmthrough planted symlinks, and every docker CLI call as dev runs withDOCKER_CONFIGoutside/home/dev, so the bind mount'sconfig.jsonandcli-pluginsare never read or run on the host.doctor hostchecks sshd's effective config unprivileged (sshd -Tagainst a throwaway ed25519 host key, asuser=dev), after first flagging a stale or missing drop-in filedevbox sync omp- applies the repo's preset,home/.omp/agent/config.yml, to both machines: the laptop's~/.omp/agent/config.ymlfirst (a differing one kept asconfig.yml.bak, since OMP may have written a change there that never reached the preset), then the devbox's overHost devbox(one.bakkept there); only the preset, never the per-machine OMP state. Refuses (without--allow-unguarded) a preset lacking a top-levelbash:block, since the copy replaces each whole file and would drop its seeded guardrail. The flag is not--forceon purpose:--forceonly ever means "past the live-session prompt"devbox sync identities- copies~/.config/devbox/identities.confinto the devbox overHost devbox, keeping oneidentities.conf.bakremotely, then re-runsbootstrap.shthere so every identity-derived file catches up; validates locally withdevbox-identities checkfirst, the same reader that runs on both sides, so a broken registry never becomes the one the devbox boots from.synchost: argument, thenDEVBOX_SSH_HOST(environment or.push.env), thendevboxdevbox agent install- idempotent: installs the same agent git override the devbox bootstraps (agent-launch,omp-launcher,claude-launcher,ghshim, credential helper, fence,devbox-gh-token,devbox-identitiesin~/.local/libexecwith a~/.local/binsymlink so it answers by name, the read-only renderedgit/agent*.gitconfig, andomp/claudesymlinks to the launchers in~/.local/libexec/devbox-agent/launchers- removing the pre-Claude~/.local/bin/ompsymlink only onceompalready resolves through that directory, since until the PATH line exists the old link is what keepsompfenced), and prints thecpcommand for~/.config/devbox/identities.confrather than seeding it; regenerates every generated file, reads/edits nothing of the user's ownidentities.confor shell rc; seeds~/.omp/agent/config.yml(the same shared preset) only when absent, reporting an existing one without abash:block; reports whetherompandclauderesolve to their launchers - printing the onePATHline for the user's shell rc until they do - and the remaining manual steps (PATs, App credentials)devbox doctor laptop- read-only counterpart ofdevbox doctor host: no private key on disk, oneid_<slug>.pub/signing_<slug>.pubpair per identity (plusdevbox.pub) held by the 1Password agent, each forge host's plain key and one.pubper identity's ssh tag selecting through it,Host <workstation>on the default identity'sid_<slug>.puband the workstation refusingdevbox.pub(everyssh -A devboxleaves that key approved for anything in the devbox; asked withssh -vand no agent, so the server answers before any signature and 1Password never prompts; its remedy is ordered - authorizeid_<default>.puband pointHost <workstation>at it untilssh <workstation> truepasses, only then removedevbox.pub, so the account is never locked out),~/.gitconfig's org includes probed with ahasconfig:remote per pattern (email, signing key,core.sshCommand), no clone left on a stale SSH-alias remote, the registry itself (devbox-identities check), every gitconfig signing viaop-ssh-signwith the rightsigning_*.pub(the base[user]name/email being the default identity's), the agent override installed - the static files byte-identical tohome/'s templates, the agent gitconfigs byte-identical to a freshdevbox-identities render agent-gitconfig- withompandclauderesolving throughdevbox-agent/launcherssymlinks (a plain file is anomp updatetakeover) and~/.local/bin/devbox-identitiesstill a symlink onto the libexec reader (the only hop that makesdevbox-identitiesresolve by name),~/.config/devbox/gitstill unwritable and no login shell writing a git identity into it, no agent token in the macOS keychain (a systemcredential.helperrunning ahead of ours), every identity's own PAT (devbox-gh-token --identity <slug>, withGH_HOSTset to that identity's host, for every identity) accepted bygh, every App pem valid, and every configured connection authenticating - includingHost <workstation>, whose hostname the repo names nowhere: it readsDEVBOX_HOSTfrom the environment or.push.env- the same per-laptop valuedevbox deployresolves, deliberately one name and one file rather than two - and skips that one check when it is unset. Unsets the launcher's exports and strips its PATH entry first, so it is meaningful from inside an agent session.file_mode(cli/lib/machine.sh) uses perl because BSD and GNUstatdisagree and both can be on a macOS PATHdevbox deploy- rsync deploy; excludes.git,.env,data/and.DS_Store, then runsbin/devbox installon the workstation (a failure there warns, never aborts).--upruns the remoteupoverssh -tso the live-session prompt is answerable;--force(which needs--up) forwards past it. No default host: it takes the argument, thenDEVBOX_HOSTfrom the environment or.push.env(gitignored), then fails - a repo going public must not ship one machine's alias as everyone's default
- Workstation (Linux host outside a container only):
-
.agents/skills/- five skills mirroring the docs for agents:devbox-basics(architecture, boundaries, entry routes),devbox-setup(five ordered setup phases plus connection failures),devbox-laptop(keys in 1Password, ssh/git config, the agent override, tokens -devbox doctor laptopas the acceptance test),devbox-deploy(sync vs apply, what a redeploy cannot destroy),devbox-cli(drivingbin/devbox: which side runs what, the session/sudo guards an agent leaves in place, reading results, changingcli/). They must stay consistent withdocs/; when a command or default changes, update both -
CLAUDE.mdand.claude/skills/- Claude Code reads neitherAGENTS.md(before v2.1.277) nor.agents/skills/, soCLAUDE.mdis the single line@AGENTS.mdand.claude/skills/<name>are relative symlinks to.agents/skills/<name>. Edit only the originals; a new skill needs its symlink