HookStackGitHub
Back to catalogue
ContextSessionStartSessionStartOn Claude Code session start· non-blocking

Auto-pull on main at session start

At session start on main/master, silently fetches the remote and fast-forward pulls if the branch is behind. If the branch diverges, injects a warning instead of pulling. Ensures the agent always works from a fresh baseline.

Use cases

  • Always-fresh baseline
  • Shared branch synchronization
  • CI/CD pre-work sync
  • Prevent stale-code edits

Providers & tags

Claude Code
#git#pull#sync#session#main-branch

settings.json fragment

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/session-start-pull-if-main.mjs",
            "type": "command"
          }
        ]
      }
    ]
  }
}

Script · .claude/hooks/session-start-pull-if-main.mjs

#!/usr/bin/env node
// SessionStart: si on est sur main/master et qu'il y a des commits distants, lance git pull
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';

function defaultExec(cmd) {
  try { return execSync(cmd, { encoding: 'utf8', timeout: 10_000 }).trim(); } catch { return ''; }
}

function defaultPull() {
  execSync('git pull --ff-only --quiet', { encoding: 'utf8', timeout: 30_000 });
}

export function run({ exec = defaultExec, pull = defaultPull } = {}) {
  const branch = exec('git branch --show-current') || exec('git rev-parse --abbrev-ref HEAD');
  if (!branch || !/^(main|master)$/.test(branch)) return null;

  // Vérifier qu'un remote existe
  const remote = exec('git remote');
  if (!remote) return null;

  // Fetch silencieux pour connaître l'état du remote
  exec('git fetch --quiet 2>/dev/null');

  const localHash = exec('git rev-parse HEAD');
  const remoteHash = exec('git rev-parse @{u} 2>/dev/null');
  if (!remoteHash || localHash === remoteHash) return null;

  // Vérifier qu'on n'est pas en avance sur le remote (merge sûr)
  const behind = exec('git rev-list HEAD..@{u} --count');
  const ahead = exec('git rev-list @{u}..HEAD --count');
  if (parseInt(behind, 10) === 0) return null;

  if (parseInt(ahead, 10) > 0) {
    // Divergence — ne pas pull automatiquement
    return [
      `## ⚠️  Branche \`${branch}\` diverge du remote`,
      `- ${behind} commit(s) en retard, ${ahead} commit(s) en avance.`,
      `- Résolvez la divergence avant de continuer (\`git pull --rebase\` ou merge manuel).`,
    ].join('\n') + '\n';
  }

  // Pull sans divergence
  try {
    pull();
  } catch {
    return `## ⚠️  \`git pull\` a échoué sur \`${branch}\` — synchronisez manuellement.\n`;
  }

  return [
    `## Dépôt synchronisé`,
    `- \`${behind}\` commit(s) récupéré(s) depuis le remote sur \`${branch}\`.`,
  ].join('\n') + '\n';
}

/* v8 ignore next 4 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
  const result = run();
  if (result) process.stdout.write(result);
}