From 94afc7a52f1a8d6be30732a02886bb50770105be Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Sun, 24 May 2026 03:23:22 -0700 Subject: [PATCH] feat(provisioner): moltbot /workspace/ is a git repo + TOOLS.md notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings moltbot agents to parity with cloud-codex on git operations. Before this, /workspace// was a plain directory — agents could not `git status`, `git commit`, or `git push` without a manual bootstrap. With the global credential helper already wired in the clawdbot postStart hook, all that was missing was `git init` on the workspace itself. - Adds ensureWorkspaceGitRepo(accountId, { gateway }) — idempotent git init + workspace-local user.name/user.email stamped with the accountId so commits attribute per-agent ("Clawdbot Agent (nova)" etc.) rather than the generic pod-global identity. - Calls it from provisionOpenClawAccount after normalizeWorkspaceDocs and ensureWorkspaceMemoryFiles. Wrapped in try/catch with non-fatal warning, matching the sibling workspace-setup steps. - Extends the TOOLS.md "Git workflow" section so agents discover the workspace-is-a-repo fact + credential wiring. Both new-file and existing- file paths handled (sed-injects on TOOLS.md from before #437). The audit's "apt-get install -y git outside the guard" step was NOT needed — git was already on PATH in the clawdbot image (verified via `kubectl exec deploy/clawdbot-gateway -- which git` → /usr/bin/git 2.39.5). Scope is ~50 LOC across one file instead of the audit's 75 across three. Closes #437. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../services/agentProvisionerServiceK8s.ts | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/backend/services/agentProvisionerServiceK8s.ts b/backend/services/agentProvisionerServiceK8s.ts index 72a0e9cfc..469b5328d 100644 --- a/backend/services/agentProvisionerServiceK8s.ts +++ b/backend/services/agentProvisionerServiceK8s.ts @@ -646,7 +646,12 @@ const normalizeWorkspaceDocs = async (accountId: any, { gateway } : any = {}) => ` if grep -q "## Git workflow" "${toolsPath}"; then`, ` sed -i "s|Default PR target branch: \`[^${'`'}]*\`|Default PR target branch: \`${DEFAULT_BRANCH}\`|g" "${toolsPath}" || true`, ` else`, - ` printf '\\n## Git workflow\\n\\n- Default PR target branch: \`${DEFAULT_BRANCH}\`\\n- All PRs must target this branch. Update when the release branch changes.\\n' >> "${toolsPath}"`, + ` printf '\\n## Git workflow\\n\\n- Your workspace at \`/workspace/${accountId}\` is a git repository — \`git status\`, \`git add\`, \`git commit\`, \`git push\`, and \`gh pr create\` all work non-interactively. Credentials are wired globally via \`/state/.git-credentials\`.\\n- For new clones, prefer \`/tmp\` over your workspace to keep the PVC small.\\n- Default PR target branch: \`${DEFAULT_BRANCH}\`\\n- All PRs must target this branch. Update when the release branch changes.\\n' >> "${toolsPath}"`, + ` fi`, + // Ensure the workspace-is-a-git-repo line is present even on TOOLS.md + // files that pre-date the workspace-git-init change (#437). + ` if ! grep -q "workspace at \\\`/workspace/${accountId}\\\` is a git repository" "${toolsPath}"; then`, + ` sed -i "/^## Git workflow$/a - Your workspace at \\\`/workspace/${accountId}\\\` is a git repository — \\\`git status\\\`, \\\`git add\\\`, \\\`git commit\\\`, \\\`git push\\\`, and \\\`gh pr create\\\` all work non-interactively. Credentials are wired globally via \\\`/state/.git-credentials\\\`." "${toolsPath}" || true`, ` fi`, `fi`, `if [ -f "${commonlySkillPath}" ]; then`, @@ -734,6 +739,40 @@ const normalizeWorkspaceDocs = async (accountId: any, { gateway } : any = {}) => return result.stdout.trim() || agentPath; }; +// Sprint follow-up (#437): moltbot workspace = git worktree. +// Idempotently `git init` the agent's `/workspace//` directory +// and stamp a workspace-local user.name / user.email so any agent that +// wants to `git commit && git push` against a real GitHub repo can do so +// without a manual bootstrap step. Credential helper is wired globally +// in the clawdbot postStart hook (see k8s/helm/.../clawdbot-deployment.yaml), +// so `git push` against `https://github.com/...` already auths via +// `/state/.git-credentials`. Cloud-codex pods get the equivalent treatment +// from their boot script — this brings moltbot to parity. +const ensureWorkspaceGitRepo = async (accountId: any, { gateway } : any = {}) => { + const podName = await resolveGatewayPodNameWithRetry(gateway); + const agentPath = `/workspace/${accountId}`; + const script = [ + 'set -eu', + `mkdir -p "${agentPath}"`, + `cd "${agentPath}"`, + 'if [ ! -d .git ]; then', + ' git init -q .', + ` git config user.name "Clawdbot Agent (${accountId})"`, + ` git config user.email "clawdbot+${accountId}@commonly.me"`, + ` echo "[provisioner] git init at ${agentPath}"`, + 'else', + ` echo "[provisioner] git already initialized at ${agentPath}"`, + 'fi', + `echo "${agentPath}"`, + ].join('\n'); + const result = await execInPod({ + podName, + containerName: 'clawdbot-gateway', + command: ['sh', '-lc', script], + }); + return result.stdout.trim().split('\n').pop() || agentPath; +}; + const ensureWorkspaceMemoryFiles = async (accountId: any, { gateway } : any = {}) => { const podName = await resolveGatewayPodNameWithRetry(gateway); const workspacePath = '/workspace'; @@ -2493,6 +2532,14 @@ const provisionOpenClawAccount = async ({ } catch (error: any) { console.warn('[k8s-provisioner] Failed to ensure workspace memory files:', (error as Error).message); } + try { + const gitPath = await ensureWorkspaceGitRepo(accountId, { gateway }); + if (gitPath) { + console.log(`[k8s-provisioner] ensured git repo for ${accountId}: ${gitPath}`); + } + } catch (error: any) { + console.warn('[k8s-provisioner] Failed to ensure workspace git repo:', (error as Error).message); + } return { configMap: configMapName,