feat(bottle): per-bottle git user.name + user.email via manifest (issue #86) #87
Reference in New Issue
Block a user
Delete Branch "bottle-git-user-config-issue-86"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #86.
Summary
Let each bottle declare a git identity (
user.name+user.email) in its manifest. At provisioning time, the backend runsgit config --globalinside the bottle so the agent's commits land with that attribution — no moregit refuses to commit until user.email is seton the first attempt.Schema
git_user:with both fields empty dies at parse time (half-finished edit hint).Changes (4 commits)
feat(manifest): add git_user bottle field— newGitUserdataclass,Bottle.git_userfield, parser,_BOTTLE_KEYSextension. 11 unit tests intest_manifest_git_user.py.feat(docker): apply git_user via git config --global on provision— new_provision_git_usersubcase inbackend/docker/provision/git.py. Runsdocker exec -u node ... git config --global user.X <value>so--globallands in/home/node/.gitconfig. 4 unit tests.feat(smolmachines): apply git_user via git config --global on provision— same pattern inbackend/smolmachines/provision/git.py, viasmolvm machine exec -e HOME=/home/node -e USER=node -- runuser -u node -- git config --global .... The HOME/USER env is load-bearing because barerunuserwithout-linherits root's HOME=/root, which would put--globalin the wrong file. 4 unit tests.docs(readme): document git_user manifest field— add example block to the bottle frontmatter sample.Tests
661 unit tests pass. New coverage:
-u node).runuser -u node --+HOME=/home/nodeenv).Manual verification path
git_user:to a bottle in~/.claude-bottle/bottles/<bottle>.md.git config --global --get user.name/... user.emailreturns the configured values.git commitworks without prompting for identity.Per-bottle `git config --global user.name` / `user.email` pair so the agent's commits inside the bottle land with a known identity rather than the agent image's default (no user, or whatever the image dropped in). Schema: git_user: name: "Eric Bauerfeld" email: "eric+claude@dideric.is" Either field can be set independently — name-only / email-only configs are valid and apply just the field that's set. An explicit `git_user:` block with both fields empty dies at parse time rather than silently no-op'ing; an omitted block is the no-op path (default GitUser is empty, provisioner skips). Parse-time validation: - Unknown sub-keys die (e.g., typo of `username`). - Non-string name/email dies. - Both-empty dies (half-finished edit hint). 11 unit tests in `test_manifest_git_user.py`; 653 unit tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Add a third provisioning subcase to `backend/docker/provision/git.py`: _provision_git_user(plan, target) Runs `docker exec -u node <container> git config --global user.{name,email} <value>` for each field the bottle's `git_user` declares. No-op when `git_user.is_empty()`. `-u node` so `--global` lands in /home/node/.gitconfig (matching the existing `_provision_git_gate_config` write location, so agent-side `git` reads both configs from the same dotfile). Name and email apply independently — a bottle declaring only name runs just the user.name line, etc. 4 unit tests in `test_docker_provision_git_user.py`: no-op, both-set, name-only, email-only. 657 unit tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>