Fetch base ref before creating a task so branches start fresh (#79)#86
Merged
Conversation
) New tasks were branched from the local origin/<base> remote-tracking ref, whatever it pointed at since the last manual fetch. If a teammate pushed since, the task silently started from a stale base and needed a manual rebase/merge later, exactly the step isolated tasks are meant to avoid. Before cutting a new branch, run a best-effort, time-bounded single-ref fetch of the base so it starts from the latest remote commit: - git_fetch_base(): git fetch --no-tags <remote> <ref>, parsed from "origin/develop" (local branches with no remote prefix are skipped). - Guarded so a dead/auth-broken/offline remote can never wedge create: GIT_TERMINAL_PROMPT=0, batch-mode SSH with a 10s connect timeout, and a 15s wall-clock deadline that SIGKILLs the child. Any failure is non-fatal: it logs and creates from the local ref (today's behavior). - Only runs when cutting a NEW branch (branch-reuse paths skip it). Wired into the single-repo and multi-repo (host + each member) create paths, each honoring its own remote/base. Runs on the existing spawn_blocking thread, so no UI-thread network wait. - fetch is refs-only: it never touches the working tree, so a dirty main checkout is unaffected. Toggle: Settings > General > "Fetch base before creating a task", default on. Users on flaky networks can opt out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019x3kbDKdUAfbCKeWhBnmJw
adamatan
added a commit
to adamatan/termic
that referenced
this pull request
Jul 12, 2026
Cross-model review (Claude structured + 4 specialists + Claude and
Codex adversarial passes) findings, all applied:
Data safety and security:
- Guard empty slugs in the remote create path: a punctuation-only task
name made wt_path the project's whole remote tasks directory and the
orphan cleanup would rm -rf every worktree in the project (Codex).
- Never rm an "orphan" path that still contains .git: path
canonicalization differences between the probed $HOME and git's own
view could classify a live worktree as an orphan.
- files_to_copy globs now pass a charset allowlist (glob syntax only)
instead of a quote/newline denylist: $(...), backticks, and ; no
longer reach the deliberately-unquoted remote for-glob (3 reviewers).
- ssh destinations are preceded by -- at all 5 argv sites so a host
value starting with - can never be parsed as an ssh option.
- Env var KEYS inlined into remote command lines are now validated as
POSIX identifiers (values were already quoted).
- Remote saves stage through a uuid temp file and verify the byte count
host-side before mv: a connection drop mid-upload can no longer
install truncated content, and concurrent saves can't collide.
- run_remote buffers are capped (64MB stdout / 1MB stderr).
Correctness:
- Remote git status errors PROPAGATE instead of collapsing to an empty
(clean-looking) panel, so the reconnect banner actually fires; the
poll also batches status+branch+log into ONE ssh round trip with a
QUICK timeout instead of three SLOW ones (Codex + performance).
- LC_ALL=C on remote git so error-substring matching ("already used by
worktree") survives non-English host locales.
- Remote create now best-effort fetches the base ref first (parity with
the local flow, simion#86) and rollback deletes a branch it created.
- project_remove aborts instead of orphaning remote worktrees when a
remote task can't be cleaned up on an unreachable host.
- Restore propagates remote rm failures instead of ignoring them.
- task_path_stat works over ssh (markdown links in remote tasks);
image/PDF preview returns a clear not-available error.
Responsiveness:
- task_open_repo and task_run_script are async + spawn_blocking; the
remote run_script path now has a hard wall-clock deadline.
- Git-status polling has an in-flight guard; the add-project dialog no
longer stacks a new ssh probe per keystroke pause while one is
running; ControlMaster teardown covers frozen task targets too.
Tests: charset guards unit-tested; sh round-trip and ControlPath
assertions cfg-gated for portability. 146 Rust tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #79.
New tasks were branched from the local
origin/<base>remote-tracking ref, whatever it pointed at since the last manual fetch. If a teammate pushed since (or you just hadn't fetched in a while), the task silently started from a stale base and needed a manual rebase/merge later, exactly the step isolated tasks are meant to avoid. Conductor already does this; this closes the parity gap.What it does
Before cutting a new branch, run a best-effort, time-bounded single-ref fetch of the base so the branch starts from the latest remote commit.
git_fetch_base():git fetch --no-tags <remote> <ref>, parsing"origin/develop"into remote + ref. Bases with no remote prefix (local branches) are skipped, and the first segment is verified against the repo's remotes sofeature/xisn't mistaken for<remote>/x.GIT_TERMINAL_PROMPT=0andGIT_SSH_COMMAND="ssh -oBatchMode=yes -oConnectTimeout=10"so a credential-less/dead remote fails fast instead of prompting, plus a 15s wall-clock deadline that SIGKILLs the child. Any failure (offline, auth-broken, timeout) is non-fatal: it logs and creates from the local ref, exactly today's behavior.spawn_blockingthread, so no UI-thread network wait.fetch(refs only), neverpull/merge/checkout, so a dirty main checkout is unaffected.Config
Settings > General > "Fetch base before creating a task", default on. Backed by a newfetch_before_createsettings field (absent = on); users on flaky networks can opt out.Per-project
.termic.yamloverride was intentionally left out to keep the surface small; easy to add later if wanted.Verification
npx tsc -bandcargo checkpass.refs/remotes/origin/<base>sogit branch --no-trackcuts from the latest remote commit, and a dead remote fails fast at the connect timeout instead of hanging.🤖 Generated with Claude Code
https://claude.ai/code/session_019x3kbDKdUAfbCKeWhBnmJw