<@U0BME18CF7S> look at the codebase in openhands/a...
# automations-debug
c
@fresh-machine-37269 look at the codebase in openhands/automation . on local backend, if i have an automation firing on a repo, does any automation clone the repo, or does it sometimes use worktrees or a preexisting clone?
šŸ‘€ 1
f
šŸ¤– On it! View progress here: oss-agent-canvas.ngrok.dev/conversations/…
āœ… Done! On the local backend, the automation service itself never clones the target repo — and it never uses worktrees or a pre-existing clone either. Here's how it actually flows: 1. The backend/dispatcher doesn't clone anything.
LocalAgentServerBackend
(
backends/local.py
) just points at the already-running agent server.
dispatcher.py
uploads a tarball (the automation's preset code) and runs it, extracting into a run-isolated work dir:
~/.openhands/workspaces/automation-runs/{run_id}/
(see
get_work_dir
). 2. The repo clone happens inside the run. The preset tarball's generated script (
presets/{plugin,prompt}/sdk_main.py
) calls
workspace.clone_repos()
, which ultimately invokes the SDK's
clone_repos()
→
_clone_single_repo()
(
openhands/sdk/workspace/repo.py
). That does a plain `git clone`: •
git clone --depth 1 [--branch <ref>] <url> <dest>
for branch/tag refs (shallow) • a full
git clone
+
checkout <sha>
for commit-SHA refs So it's a fresh clone into the run's isolated workspace each time — no worktree, no reuse of a prior clone. 3. Worktrees exist in the SDK but aren't used here. The SDK's
StartConversationRequest
has a
worktree
flag (default
False
) that would create a dedicated worktree under
/tmp/conversation-worktrees/...
. The automation's generated script never sets it, so automations never take that path. The one place a "preexisting clone" is reused: the separate git-sync feature (
git_sync/client.py
,
ensure_repo
). But that's a different mechanism — it maintains a persistent checkout of the config repo that stores automation definitions (prompts/plugins) so it can round-trip edits back to git. It clones once and reuses that working copy on subsequent sync cycles. It is not how a fired automation obtains the repo it's supposed to act on. TL;DR — local automations do a fresh, shallow-where-possible
git clone
inside the run's isolated workspace. No worktrees, no shared pre-existing clone.
c
@fresh-machine-37269 so these Automations with preset (plugin, prompt), lets say we defined one, and set it, and it runs once. it clones. then when it runs again, does it clone again? does it run in the same workspace as the first run, or not? whats the workspace name
šŸ‘€ 1
f
āœ… Done! Yes — it clones again every run, and no, it does not run in the same workspace. Here are the details: Each run gets a brand-new workspace. • Every firing creates a new
AutomationRun
row with a fresh random
UUID
(
models.py
,
id = uuid.uuid4
). •
dispatcher.py
passes that run id into
backend.get_work_dir(run_id)
, and local mode resolves it to:~/.openhands/workspaces/automation-runs/{run_id}/(base dir configurable via `WORKSPACE_BASE`/`AUTOMATION_WORKSPACE_BASE`, defaults to
~/.openhands/workspaces
). The workspace name is the run's UUID — literally
automation-runs/<uuid>
. Two firings = two different UUIDs = two different directories. There's no reuse by automation name or by "first run". It clones fresh each time. • Inside the run, the preset script reads
repos_config.json
and calls
workspace.clone_repos(repos_config)
with no explicit target, so it clones into the run's own
working_dir
(that UUID-named folder). •
clone_repos()
→
_clone_single_repo()
runs a plain
git clone
(shallow
--depth 1
for branch/tag refs, full clone for commit SHAs) into a repo-named subfolder under that directory. So: run 1 → clone into
~/.openhands/workspaces/automation-runs/<uuid-1>/<repo>/
, run 2 → fresh clone into
~/.openhands/workspaces/automation-runs/<uuid-2>/<repo>/
. Nothing is persisted or reused between runs, and it never uses worktrees or a pre-existing clone.
c
@fresh-machine-37269 look at your own Automations, in particular Slack Automation: of what type is it, prompt, preset, or?
šŸ‘€ 1
f
āœ… Done! I checked the automation database. There are three Slack-related automations: 1. Slack Channel Monitor - All Joined Channels 2. Slack Channel Monitor - OpenHands AI Authorized 3. Agent Canvas weekly KPI Slack summary (this one posts to Slack) The two "Slack Channel Monitor" automations (which match your "Slack Automation" the closest) are custom SDK automations — neither a prompt nor a plugin preset. The tell-tale signs in the DB: •
prompt
column is
NULL
(preset prompt automations set this). •
preset_metadata
is
NULL
(preset automations populate this). •
entrypoint
is
python3 main.py
(preset automations use
.venv/bin/python main.py
, since presets build their own venv). • Their tarball upload names are
slack-channel-monitor-all-joined
/
slack-monitor-openhands-ai.tar.gz
— not the
prompt-automation-…
or
plugin-automation-…
prefixes that
preset_router.py
assigns when creating presets. So: custom SDK code, uploaded as a tarball, not a preset. (The third, "Agent Canvas weekly KPI Slack summary," is instead a prompt preset — created with
prompt-automation-…
and a
.venv/bin/python main.py
entrypoint.) One caveat: I'm reading from the sqlite export at
/tmp/automations-export/automations.db
, so this reflects that snapshot — if the live DB differs it could be out of date.