Skip to content

Fetch base ref before creating a task so branches start fresh (#79)#86

Merged
simion merged 1 commit into
mainfrom
feat/fetch-base-before-create-79
Jul 11, 2026
Merged

Fetch base ref before creating a task so branches start fresh (#79)#86
simion merged 1 commit into
mainfrom
feat/fetch-base-before-create-79

Conversation

@simion

@simion simion commented Jul 11, 2026

Copy link
Copy Markdown
Owner

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 so feature/x isn't mistaken for <remote>/x.
  • Can never wedge create: GIT_TERMINAL_PROMPT=0 and GIT_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.
  • Only when cutting a new branch (branch-reuse paths skip it, since fetch wouldn't move an existing branch point). Wired into the single-repo path and the multi-repo path (host + each member), each honoring its own remote/base. Runs on the existing spawn_blocking thread, so no UI-thread network wait.
  • Working-tree safe: it's a bare fetch (refs only), never pull/merge/checkout, so a dirty main checkout is unaffected.

Config

Settings > General > "Fetch base before creating a task", default on. Backed by a new fetch_before_create settings field (absent = on); users on flaky networks can opt out.

Per-project .termic.yaml override was intentionally left out to keep the surface small; easy to add later if wanted.

Verification

  • npx tsc -b and cargo check pass.
  • Verified against throwaway repos: a single-ref fetch updates refs/remotes/origin/<base> so git branch --no-track cuts 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

)

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
@simion simion merged commit 067c645 into main Jul 11, 2026
2 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Fetch origin/<base> before creating a workspace so it branches from an up-to-date ref

1 participant