Skip to content

feat(cms): auto-switch stale session branches to a fresh one on CMS open - #6639

Merged
guitavano merged 1 commit into
mainfrom
guitavano/cms-auto-fresh-branch
Aug 28, 2026
Merged

feat(cms): auto-switch stale session branches to a fresh one on CMS open#6639
guitavano merged 1 commit into
mainfrom
guitavano/cms-auto-fresh-branch

Conversation

@guitavano

@guitavano guitavano commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

O que é

Quando alguém abre o CMS numa branch de sessão cujo último commit é mais antigo que a janela de staleness (2 dias), a sessão troca automaticamente para uma branch nova cortada da default branch. A branch velha não é apagada nem alterada — continua no GitHub, recuperável.

Motivação: alguém some por ~15 dias e volta pra uma branch defasada/quebrada; em vez disso passa a cair numa branch limpa e atual.

  • Gated pela flag de org cms_auto_fresh_branch (default OFF) — nada muda até uma org ligar explicitamente.
  • Uma troca por thread/aba (guarda por ref, mesmo padrão do branch-adopt que já existia).
  • Dispara só em runtime === "cms".

Por que "data do último commit" e não "data de criação"

Git não guarda quando um ref nasceu e a API do GitHub não expõe isso de forma confiável. O sinal robusto — sem depender do nome da branch — é a data do commit para o qual o head aponta. Isso pega inclusive a branch nunca editada: o ref fica congelado no commit da main de quando nasceu, então uma branch de 15 dias tem head com data velha e é detectada como stale, mesmo sem nenhum commit próprio.

Mudanças

  • api: getBranchHead no git-data client; decofile /status agora devolve lastCommitAt (null para a default branch e branches ainda não materializadas → nunca troca).
  • web: predicado puro isBranchStale + query de status do decofile; efeito one-shot de troca no VmEventsBridge.
  • shared: flag cms_auto_fresh_branch + contratos regenerados.
  • e2e: stub do GitHub ganhou datas de commit + rota /branches/{branch}; novo teste de contrato do /status; asserts antigos do status atualizados para o campo novo.
  • unit: isBranchStale.

Como ligar

ORGANIZATION_SETTINGS_UPDATE { flags: { cms_auto_fresh_branch: true } }

Testing

  • tsc (api/web/shared/e2e), fmt, lint (0 erros), knip limpo
  • ✅ unit isBranchStale (7/7)
  • ⚠️ e2e escrito e type-checa; não rodado localmente (harness e2e exige Postgres em :5432) — roda no CI.

Caveat

Se a própria main ficar parada +2 dias, trocar uma branch limpa-porém-antiga geraria outra branch limpa (churn inofensivo, sem perda de dado) — irrelevante em repo ativo. Se incomodar, o passo seguinte é o servidor gravar branchCreatedAt no momento de criação do ref.

🤖 Generated with Claude Code


Summary by cubic

Auto-switches the CMS session to a fresh branch when the current branch's last commit is older than 2 days, so returning users don't keep working on a lagging branch. The stale branch stays intact on GitHub; the behavior is off by default behind the cms_auto_fresh_branch org flag.

Changes

  • Adds getBranchHead to the git-data client and lastCommitAt to the decofile /status response (null for default/unmaterialized branches).
  • Adds a pure isBranchStale check and a one-shot switch effect in VmEventsBridge, gated on runtime === "cms".
  • Extends the GitHub e2e stub with commit dates and a /branches/{branch} route; adds a /status contract test and unit tests for isBranchStale.
  • Orgs opt in via ORGANIZATION_SETTINGS_UPDATE with cms_auto_fresh_branch: true.

Written for commit 6d5c9d1. Summary will update on new commits.

Review in cubic

@github-actions github-actions Bot added the claude PR authored by a coding agent label Aug 27, 2026
Opening the CMS on a branch whose head commit is older than the staleness
window (2 days) moves the session to a freshly minted branch cut from the
default branch. The stale branch is left intact on GitHub (recoverable).
Gated by the org flag cms_auto_fresh_branch (default off); one switch per
thread per tab, mirroring the existing branch-adopt guard.

Git stores no ref-creation time, so the signal is the branch head commit's
date: a never-edited side branch keeps the (old) base commit it was cut
from, so it is still detected as stale without any commits of its own.

- api: getBranchHead on the git-data client; decofile /status now returns
  lastCommitAt (null for the default branch and unmaterialized branches).
- web: pure isBranchStale predicate + decofile status query; one-shot
  switch effect in VmEventsBridge, gated on runtime === "cms" and the flag.
- e2e: github stub grows commit dates + a /branches/{branch} route; new
  /status contract test; existing status assertions updated for the field.
- unit: isBranchStale.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@guitavano
guitavano force-pushed the guitavano/cms-auto-fresh-branch branch from eb6ea8a to 6d5c9d1 Compare August 28, 2026 12:29
@guitavano
guitavano merged commit 3d51d27 into main Aug 28, 2026
34 checks passed
@guitavano
guitavano deleted the guitavano/cms-auto-fresh-branch branch August 28, 2026 12:51
decocms Bot pushed a commit that referenced this pull request Aug 28, 2026
PR: #6639 feat(cms): auto-switch stale session branches to a fresh one on CMS open
Bump type: minor

- decocms (apps/api/package.json): 4.290.1 -> 4.291.0
- @decocms/native (apps/native/package.json): 4.290.1 -> 4.291.0
- @decocms/e2e (packages/e2e/package.json): 1.56.0 -> 1.57.0
- @decocms/shared (packages/shared/package.json): 0.66.0 -> 0.67.0

Deploy-Scope: both
pedrofrxncx added a commit that referenced this pull request Aug 28, 2026
…McpId) call (#6678)

VmEventsBridge called useSessionRuntime(virtualMcpId) twice with identical
args - once for the auto-fresh-branch staleness check (#6639), once further
down for the sandbox events gate. Both calls resolve to the same answer for
the same render; the second was pure recompute. Reuse the first result.
guitavano added a commit that referenced this pull request Aug 28, 2026
…p) (#6680)

After #6667 started addressing destinations by path, a repo-backed agent
editor could open with no thread in the URL (`?thread=` absent — a deep
link, choose-editor, or any navigation that didn't carry it, since the
shell deliberately doesn't retain `thread`). The branch is a thread field,
so a threadless editor stranded the branch picker on "Select branch…" with
selecting and "New" both silently no-op (setCurrentTaskBranch is gated on
an active thread), and the adopt/auto-fresh effects never fired.

AgentInsetProvider now guarantees a thread for repo-backed agents: when the
URL names none it mints one into `?thread=` before mounting, reusing the
user's idle empty "New chat" for that agent when one exists — exactly what
useNavigateToAgent does (findReusableNewChat ?? fresh id). This restores the
pre-#6667 behavior where the client minted the thread id and the ensure
fallback created the row on landing. Scoped to repo-backed agents; the Super
Agent keeps its lazy threadless composer.

Complements #6639: with a thread guaranteed, adopt-branch mints a fresh
(unmaterialized, non-stale) branch and the auto-fresh-stale check evaluates
correctly instead of sitting dead on a threadless entry.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude PR authored by a coding agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant