🌿

09-Git-Workflow

🌿

Branch-per-run. Snapshots before changes. Push is always a deliberate user action.

1. Service

App\Services\Git\GitService wraps the git binary. All calls go through CommandExecutionService so they're logged in command_executions and produce events.

interface GitService {
	public function clone(Project $p, string $repoUrl): GitResult;
	public function pull(Project $p): GitResult;
	public function status(Project $p): GitResult;
	public function createBranch(Project $p, string $branchName, ?string $from = null): GitResult;
	public function checkout(Project $p, string $ref): GitResult;
	public function diff(Project $p, ?string $base = null): GitResult;
	public function commit(Project $p, string $message, array $paths = []): GitResult;
	public function push(Project $p, string $remote = 'origin', ?string $branch = null): GitResult; // gated
	public function restoreCommit(Project $p, string $commitHash): GitResult;
	public function currentBranch(Project $p): string;
	public function workingTreeClean(Project $p): bool;
}

2. Branch naming

agent/run-{run_id}-{short-slug} where the slug is Str::slug(run.title) truncated to 24 chars.

Examples:

  • agent/run-148-add-invoices-endpoint
  • agent/run-149-fix-auth-redirect

The default branch (main/master) is never modified by an agent.

3. Pre-run sequence

flowchart TD
	A["Run start"] --> B["workingTreeClean?"]
	B -- no --> X["emit error<br>fail run"]
	B -- yes --> C["create pre_run snapshot<br>(commit hash on default branch)"]
	C --> D["createBranch agent/run-{id}-{slug}"]
	D --> E["checkout new branch"]
	E --> F["emit git_branch_created"]
	F --> G["Run proceeds"]

4. During run

  • File edits are applied via WorkspaceService::applyChange(). They are not auto-committed unless safety.auto_git_commit=true.
  • The agent can call git(commit, …) tool to checkpoint logical steps. Each commit produces a git_operations row + command_completed event.
  • The agent can call git(diff) at any time. Result goes to the console as git_diff_created.

5. End of run

Three paths:

  1. Auto-commit OFF, final summary only: changes remain in the working tree on the run branch (or uncommitted, depending on agent behavior). User reviews the diff.
  1. Auto-commit ON (agent setting): a final commit Agent run #{id}: {title} is created on the run branch with all outstanding changes.
  1. Manual approval → commit: user posts POST /api/runs/{run}/approve with { commit: true } → server commits.

In all cases, push is never automatic. Push requires POST /api/runs/{run}/push.

6. Push gating

  • safety.auto_git_push must be true and the run must have an approved run_review and the API token must have ability git:push. All three are required.
  • If any check fails: emit command_failed with was_blocked=true, message "push blocked: <reason>".

7. Rollback

  • restoreCommit(hash) is invoked by SnapshotService::restore(snapshot_id).
  • The current run branch is reset to the snapshot's commit hash; uncommitted changes are stashed into agent/stash-run-{id}-{timestamp} for recovery.
  • A snapshot_created event with description: "restore checkpoint" is written.

8. Conflict policy

If a git pull introduces conflicts, the orchestrator:

  1. Aborts the pull (git merge --abort / git rebase --abort).
  1. Emits an error event with the conflicting files.
  1. Transitions the run to waiting_for_user asking the user to resolve manually (no automatic conflict resolution by the agent).

9. Allowed Git operations matrix

OperationAllowed by defaultRequires approval
status
diff
branch (create/checkout)
add / commit (on run branch)
pull (default branch)
restore (snapshot)
pushreview + token ability
force pushhard-blocked
tag pushhard-blocked
branch delete (remote)hard-blocked

10. Git workflow diagram

gitGraph
	commit id: "main"
	branch agent/run-148
	commit id: "pre_run snapshot"
	commit id: "feat: invoices controller"
	commit id: "feat: invoice migration"
	commit id: "test: invoices feature test"
	checkout main
	merge agent/run-148 tag: "v1.4.0 (user-approved)"

11. Repository URL validation

  • https:// and git@ schemes allowed.
  • Hostname allowlist configurable (git.allowed_hosts, default github.com, gitlab.com, bitbucket.org).
  • No file://, no relative paths.
  • No credentials in URLs (https://user:pass@… is rejected; use a GitHub App / deploy key configured via project_connections).