Back to catalogue
WorkflowSessionStartSessionStartOn Claude Code session start· non-blocking
Auto-create worktree when starting on main
Start on main and you're moved to a worktree
At session start, detects if the current branch is main/master and automatically creates a dated git worktree (work/session-YYYYMMDD). The agent is informed of the new path and instructed to work there instead of the main repo.
What does the Auto-create worktree when starting on main hook do?
Auto-create worktree when starting on main is a Claude Code SessionStart hook. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Start on main and you're moved to a worktree.
Use cases
- Branch discipline
- Isolated sessions
- Accidental main commits prevention
- Multi-session workflows
Tags
#git#worktree#safety#session#main-branch
settings.json fragment
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/session-start-worktree-if-main.mjs",
"type": "command"
}
]
}
]
}
}Script · .claude/hooks/session-start-worktree-if-main.mjs
#!/usr/bin/env node
// SessionStart: crée un worktree isolé si la session démarre sur main/master.
// Ne supprime JAMAIS de worktree : un worktree mergé peut encore héberger une session
// active/référencée — le supprimer ferait « disparaître » cette session. Le nettoyage
// des worktrees mergés reste une opération manuelle (`git worktree prune`).
import { execSync } from 'child_process';
import { existsSync } from 'fs';
import { fileURLToPath } from 'url';
function defaultExec(cmd) {
try { return execSync(cmd, { encoding: 'utf8', timeout: 10_000 }).trim(); } catch { return ''; }
}
function defaultAddWorktree(path, branchName) {
execSync(`git worktree add "${path}" -b "${branchName}"`, {
encoding: 'utf8',
timeout: 15_000,
stdio: ['ignore', 'ignore', 'ignore'],
});
}
export function run({
exec = defaultExec,
addWorktree = defaultAddWorktree,
exists = existsSync,
now = () => new Date(),
} = {}) {
const branch = exec('git branch --show-current') || exec('git rev-parse --abbrev-ref HEAD');
if (!branch || !/^(main|master)$/.test(branch)) return null;
const currentRoot = exec('git rev-parse --show-toplevel');
if (!currentRoot) return null;
// Ne pas agir si on est déjà dans un worktree secondaire
const worktreeList = exec('git worktree list');
const mainRoot = worktreeList.split('\n')[0]?.split(/\s+/)[0] ?? '';
if (mainRoot !== currentRoot) return null;
// Synchroniser main avec le remote avant de créer le worktree
exec('git fetch --quiet origin main');
exec('git merge --ff-only origin/main');
const date = now().toISOString().slice(0, 10).replace(/-/g, '');
const branchName = `work/session-${date}`;
const worktreePath = `${currentRoot}/.claude/worktrees/session-${date}`;
// Vérifier si un worktree pour aujourd'hui existe encore (non mergé)
const freshList = exec('git worktree list');
const todayLine = freshList.split('\n').slice(1).find((l) => l.includes(branchName));
if (todayLine) {
const wtPath = todayLine.split(/\s+/)[0];
return [
`## Worktree session existant`,
`- Session démarrée sur \`main\`. Le worktree du jour est déjà actif.`,
`- **Chemin** : \`${wtPath}\``,
`- **Branche** : \`${branchName}\``,
`- Effectuez vos modifications dans ce worktree, pas dans le dépôt principal.`,
].join('\n') + '\n';
}
// Créer un worktree frais depuis main
try {
addWorktree(worktreePath, branchName);
} catch {
return [
`## ⚠️ Session démarrée sur \`main\``,
`- Impossible de créer un worktree automatiquement (branche \`${branchName}\` peut-être déjà existante).`,
`- Créez manuellement un worktree ou une branche avant de modifier des fichiers.`,
].join('\n') + '\n';
}
if (!exists(worktreePath)) return null;
return [
`## Worktree isolé créé automatiquement`,
`- Session démarrée sur \`main\` : un worktree a été créé pour isoler les modifications.`,
`- **Chemin** : \`${worktreePath}\``,
`- **Branche** : \`${branchName}\``,
`- Travaillez dans ce worktree — évitez de modifier des fichiers dans le dépôt principal.`,
].join('\n') + '\n';
}
/* v8 ignore next 4 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const result = run();
if (result) process.stdout.write(result);
}