Back to catalogue
WorkflowSessionStartSessionStartOn Claude Code session start· non-blocking
Auto-create worktree when starting on main
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.
Use cases
- Branch discipline
- Isolated sessions
- Accidental main commits prevention
- Multi-session workflows
Providers & tags
Claude Code
#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.
// Nettoie automatiquement les worktrees dont la branche a été mergée dans main.
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'],
});
}
function defaultRemoveWorktree(mainRoot, wtPath, branchName) {
try { execSync(`git -C "${mainRoot}" worktree remove --force "${wtPath}"`, { timeout: 10_000 }); } catch { /* ignore */ }
try { execSync(`git -C "${mainRoot}" branch -D "${branchName}"`, { timeout: 5_000 }); } catch { /* ignore */ }
}
export function run({
exec = defaultExec,
addWorktree = defaultAddWorktree,
removeWorktree = defaultRemoveWorktree,
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');
// Nettoyer les worktrees dont la branche est fusionnée dans origin/main
const mergedBranches = new Set(
exec('git branch --merged origin/main')
.split('\n')
.map((b) => b.trim().replace(/^\*\s*/, ''))
.filter(Boolean),
);
const secondaryLines = exec('git worktree list').split('\n').slice(1);
for (const line of secondaryLines) {
if (!line.trim()) continue;
const parts = line.split(/\s+/);
const wtPath = parts[0];
const wtBranch = (parts[2] ?? '').replace(/^\[|\]$/g, '');
if (wtBranch && mergedBranches.has(wtBranch)) {
removeWorktree(mainRoot, wtPath, wtBranch);
}
}
const date = now().toISOString().slice(0, 10).replace(/-/g, '');
const projectName = currentRoot.split('/').pop() ?? 'project';
const branchName = `work/session-${date}`;
const worktreePath = `${currentRoot}/../${projectName}-work-${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);
}