TL;DRClaude Code accepte désormais des mods : de petits modules JavaScript qui s'exécutent à l'intérieur de l'outil. Parmi leurs possibilités, l'une change la façon de travailler à plusieurs sessions : un mod peut envoyer et recevoir des messages d'une session à l'autre. Voici comment cela fonctionne, avec un exemple complet à reprendre dans vos projets.
Un mod, c'est quoi ?#
Un mod est un plugin Claude Code qui modifie l'apparence et le comportement de l'outil lui-même. Il est écrit en JavaScript ou en TypeScript, sous la forme de fonctions que Claude Code appelle à chaque événement : un outil sur le point d'être lancé, un prompt envoyé, une zone de l'interface à dessiner, un message qui arrive d'une autre session.
Pour chaque événement, la fonction choisit entre trois attitudes : observer (noter ce qui se passe et laisser faire), réécrire (modifier l'événement avant qu'il continue) ou répondre à la place de Claude Code. C'est ce qui distingue un mod des hooks que vous configurez déjà dans settings.json : ceux-là lancent un script extérieur, alors qu'un mod s'exécute à l'intérieur de Claude Code. La documentation appelle d'ailleurs les anciens « settings hooks » pour éviter la confusion.
Les mods sont actifs par défaut à partir de Claude Code 2.1.287 dans le terminal, et de la version 2.1.286 dans l'onglet Code de l'application de bureau. Si vous débutez avec l'outil, commencez par notre guide pour structurer un projet Claude Code : il explique où se rangent consignes, skills et hooks.
Mod, hook de réglages, skill ou serveur MCP ?#
| Mod | Hook de réglages | Skill | Serveur MCP | |
|---|---|---|---|---|
| Ce que c'est | Des fonctions exécutées dans Claude Code | Une commande shell, une requête HTTP ou un prompt | Un fichier d'instructions que Claude lit | Un service externe qui fournit des outils |
| Ce qu'il peut changer | Appels d'outils, prompts, commandes, tours, interface, messages entre sessions | Autoriser, bloquer ou journaliser un événement | Ce que Claude sait faire | Les outils dont Claude dispose |
| Dessine dans l'interface | Oui | Non | Non | Non |
| Ce que vous écrivez | JavaScript ou TypeScript | Un script et une entrée de réglages | Du Markdown | Un serveur, dans le langage de votre choix |
Ce que les mods apportent aux sessions multiples#
Travailler avec plusieurs sessions en parallèle est devenu courant : une session sur l'API, une autre sur le front, une troisième qui surveille une migration. Claude Code sait déjà faire passer un message de l'une à l'autre. Il suffit de le demander en langage courant, par exemple « préviens @api-worker que la migration est terminée », et Claude s'en charge avec ses outils ListAgents et SendMessage. La commande /list-agents affiche les sessions joignables.
La nouveauté, c'est que cette messagerie est désormais accessible au code d'un mod. Vous n'êtes plus obligé d'attendre que Claude pense à prévenir l'autre session : votre mod peut envoyer le message lui-même, réagir à ceux qui arrivent, et tenir à jour un état commun à toutes les sessions de la machine.

Trois briques de l'API suffisent pour relier des sessions :
| Brique | Rôle | À savoir |
|---|---|---|
$.session.send({ to, text }) | Envoie un message texte à une autre session, à un sous-agent ou à un coéquipier | Répond isDelivered à vrai dès que le message est déposé dans la file du destinataire, pas quand il est lu |
Événement session.receive | Se déclenche à l'arrivée d'un message, avant que Claude le lise | e.origin.kind indique la provenance : autre session, notification de tâche, déclencheur planifié |
Événement session.send | Se déclenche quand un message s'apprête à partir | Permet de le réécrire, de le rediriger ou de le refuser |
$.store | Stockage clé-valeur du plugin, conservé d'une session à l'autre | Partagé par toutes les sessions de la machine qui chargent le mod, 4 Mio de JSON au total |
Bon à savoir. Entre deux sessions de la même machine, le message transite par un socket local propre à chaque session, sans passer par les serveurs d'Anthropic. Pour joindre une session sur une autre machine ou dans le cloud, il faut que la session soit connectée à Remote Control, et le message passe alors par les serveurs d'Anthropic.
Mise en œuvre : un mod qui relie vos sessions#
Nous avons écrit un petit mod, relais-sessions, pour montrer le mécanisme de bout en bout. Il fait trois choses : chaque session s'inscrit dans un annuaire commun, trois commandes permettent de lister les sessions et de leur écrire, et une notification signale chaque message reçu.
1. Vérifier la version#
claude --versionIl faut la version 2.1.287 ou une version plus récente. Notre exemple a été validé avec la 2.1.296.
2. Créer les trois fichiers#
Un mod tient en trois fichiers, sans Node.js à installer ni étape de compilation : Claude Code charge directement les fichiers .js et .ts.
relais-sessions/
├── .claude-plugin/
│ └── plugin.json
└── hooks/
├── hooks.json
└── register.jsLe manifeste, .claude-plugin/plugin.json :
{
"name": "relais-sessions",
"version": "0.1.0",
"description": "Relie les sessions Claude Code ouvertes sur la machine : annuaire partagé et messages entre sessions",
"author": { "name": "Websource" }
}Le fichier hooks/hooks.json indique où se trouve le code. C'est la clé modules qui fait d'un plugin un mod :
{
"description": "Module du mod relais-sessions",
"modules": ["./register.js"]
}3. Écrire le module#
Voici hooks/register.js en entier. Chaque fonction passée à on reçoit trois arguments : $ (l'API des mods), e (l'événement) et next (qui passe la main aux autres mods puis à Claude Code).
// Une clé par session dans l'annuaire partagé
const PREFIX = 'session:'
// Sans signe de vie depuis 2 minutes, une session est retirée de l'annuaire
const STALE_MS = 120000
// Messages reçus depuis le chargement du mod
let received = 0
// Inscrit (ou rafraîchit) cette session dans l'annuaire
async function announce($) {
const id = await $.session.id()
await $.store.set(PREFIX + id, { id, cwd: await $.session.cwd(), seenAt: await $.clock.now() })
}
// Renvoie les autres sessions encore actives
async function peers($) {
const me = await $.session.id()
const now = await $.clock.now()
const list = []
for (const key of await $.store.keys()) {
if (!key.startsWith(PREFIX)) continue
const peer = await $.store.get(key)
if (!peer || peer.id === me) continue
if (now - peer.seenAt > STALE_MS) {
await $.store.delete(key)
continue
}
list.push(peer)
}
return list
}
// Met à jour la ligne affichée sous le prompt
async function refresh($) {
const count = (await peers($)).length
$.ui.status(count + ' session(s) reliée(s) · ' + received + ' message(s) reçu(s)')
}
export function register(on) {
on('session.start', async ($, e, next) => {
await announce($)
// Signe de vie toutes les 30 secondes, sans démarrer de tour
$.clock.every(30000, async () => {
await announce($)
await refresh($)
})
await $.command.register({ name: 'relais', description: 'Lister les sessions reliées' })
await $.command.register({ name: 'relais-dire', description: 'Envoyer un message à une session', argumentHint: '<début-id> <message>' })
await $.command.register({ name: 'relais-tous', description: 'Envoyer un message à toutes les sessions', argumentHint: '<message>' })
return next(e)
})
// À la fermeture, la session se retire de l'annuaire
on('session.end', async ($, e, next) => {
await $.store.delete(PREFIX + (await $.session.id()))
return next(e)
})
on('command.run', { command: 'relais' }, async ($) => {
const list = await peers($)
if (list.length === 0) return { text: 'Aucune autre session reliée pour le moment.' }
return { text: list.map((p) => p.id.slice(0, 8) + ' ' + p.cwd).join('\n') }
})
on('command.run', { command: 'relais-dire' }, async ($, e) => {
const [start, ...words] = e.args.trim().split(/\s+/)
const matches = (await peers($)).filter((p) => p.id.startsWith(start))
if (matches.length !== 1) return { text: matches.length + ' session(s) pour « ' + start + ' » : lancez /relais pour vérifier.' }
const sent = await $.session.send({ to: { sessionId: matches[0].id }, text: words.join(' ') })
return { text: sent.isDelivered ? 'Message déposé.' : 'Non remis : ' + sent.reason }
})
on('command.run', { command: 'relais-tous' }, async ($, e) => {
let delivered = 0
const list = await peers($)
for (const peer of list) {
const sent = await $.session.send({ to: { sessionId: peer.id }, text: e.args })
if (sent.isDelivered) delivered += 1
}
return { text: delivered + ' session(s) sur ' + list.length + ' ont reçu le message.' }
})
// Un message arrive d'une autre session : on le signale, puis on le laisse passer
on('session.receive', async ($, e, next) => {
if (e.origin.kind === 'peer' || e.origin.kind === 'peer-send-message') {
received += 1
$.ui.toast('Message d’une autre session : ' + e.text.slice(0, 60))
await refresh($)
}
return next(e)
})
}Quelques choix méritent une explication :
- Une clé par session dans
$.store. Une lecture suivie d'une écriture n'est pas atomique : si deux sessions modifiaient la même liste, la seconde écraserait la première. Avec une clé par session, chacune n'écrit que la sienne. - Un signe de vie toutes les 30 secondes.
$.clock.everyremplacesetInterval, qui n'existe pas dans un mod. Le minuteur tourne entre les tours sans en démarrer un, donc sans consommer de tokens. return next(e)à la réception. Le mod signale le message puis le laisse passer : Claude le lira normalement. Renvoyer{ consumed: 'raison' }le retiendrait au contraire.- Le résultat de l'envoi est toujours vérifié.
$.session.sendne lève pas d'erreur quand la session visée est fermée ou refuse les messages : il répondisDeliveredà faux, avec la raison.
4. Charger le mod et s'en servir#
Ouvrez deux terminaux et lancez une session dans chacun, en lui donnant un nom lisible :
# Terminal 1
claude --name api --plugin-dir ./relais-sessions
# Terminal 2
claude --name front --plugin-dir ./relais-sessionsL'option --plugin-dir charge le dossier pour la session en cours, sans rien installer, et recharge le module à chaque enregistrement du fichier. Dans chaque session, /plugin affiche une ligne du type 1 mod active · relais-sessions. Vous disposez alors de trois commandes :
/relaisliste les autres sessions, avec le début de leur identifiant et leur dossier de travail./relais-dire a1b2 La migration est terminée, tu peux rebaserécrit à la session dont l'identifiant commence para1b2./relais-tous Je touche au schéma de la table clientsprévient toutes les sessions d'un coup.
Côté destinataire, le message apparaît dans la conversation sous la forme d'une ligne d'aperçu, puis Claude le lit : entre deux appels d'outils si un tour est en cours, ou dans un nouveau tour si la session était au repos. Un message reçu consomme donc de l'usage, comme un prompt que vous auriez tapé.
5. Valider et tester#
Avant de charger un mod, le vôtre ou celui de quelqu'un d'autre, claude plugin validate liste ce qu'il écoute et ce qu'il appelle, sans exécuter son code. Voici l'essentiel de la sortie obtenue sur notre exemple :
claude plugin validate ./relais-sessions
❯ ./register.js hooks: session.start, session.end, command.run{command=relais}, command.run{command=relais-dire}, command.run{command=relais-tous}, session.receive
❯ ./register.js calls: $.clock.every, $.clock.now (via announce, peers), $.command.register, $.session.cwd (via announce), $.session.id, $.session.send, $.store.delete, $.store.get (via peers), $.store.keys (via peers), $.store.set (via announce), $.ui.status (via refresh), $.ui.toast
✔ Validation passedLes mods se testent aussi sans ouvrir de session, sans connexion et sans réseau. Le test ci-dessous, à enregistrer dans tests/relais.test.ts, simule l'annuaire et l'horloge, puis vérifie que la diffusion atteint bien les deux sessions inscrites :
import { expect, mock, test } from 'claude-code/testing'
test('/relais-tous envoie le message à chaque session de l’annuaire', async ($, on) => {
// Horloge et annuaire en mémoire : deux autres sessions déjà inscrites
mock.clock(on, { now: 1000 })
mock.store(on, {
'session:aaaa1111': { id: 'aaaa1111', cwd: '/srv/api', seenAt: 1000 },
'session:bbbb2222': { id: 'bbbb2222', cwd: '/srv/front', seenAt: 1000 },
})
// Répond à la place de Claude Code : l'identifiant de la session, puis l'envoi
on('session.id', () => ({ value: 'moi00000' }))
const sent: string[] = []
on('session.send', (_$, e) => {
sent.push(e.text)
return { isDelivered: true }
})
const answer = await $.command.run({ command: 'relais-tous', args: 'La migration est terminée' })
expect(answer.text).toBe('2 session(s) sur 2 ont reçu le message.')
expect(sent).toEqual(['La migration est terminée', 'La migration est terminée'])
})
test('/relais ignore une session sans signe de vie depuis plus de 2 minutes', async ($, on) => {
mock.clock(on, { now: 500000 })
mock.store(on, {
'session:aaaa1111': { id: 'aaaa1111', cwd: '/srv/api', seenAt: 499000 },
'session:cccc3333': { id: 'cccc3333', cwd: '/srv/vieux', seenAt: 1000 },
})
on('session.id', () => ({ value: 'moi00000' }))
const answer = await $.command.run({ command: 'relais', args: '' })
expect(answer.text).toBe('aaaa1111 /srv/api')
})claude plugin test
tests/relais.test.ts:
(pass) /relais-tous envoie le message à chaque session de l’annuaire
(pass) /relais ignore une session sans signe de vie depuis plus de 2 minutes
2 pass
0 failCe que nous avons vérifié. Le mod de cet article passe la validation et ses deux tests avec Claude Code 2.1.296. L'API des mods évolue d'une version à l'autre : en cas de doute, fiez-vous aux fichiers de types que Claude Code écrit dans le dossier
.claude-plugin/types/de votre mod, ils décrivent exactement votre version.
L'intégrer dans un projet#
Laisser Claude l'écrire pour vous#
Vous n'êtes pas obligé de coder le mod à la main. Dans une session interactive, décrivez ce que vous voulez, par exemple « fais un mod qui prévient mes autres sessions quand je modifie un fichier de migration ». Claude s'appuie sur un skill intégré, plugin-authoring, et écrit le mod dans ~/.claude/dev-mods/, dans un sous-dossier au nom de la session. À la création du premier fichier, Claude Code vous demande s'il doit activer le rechargement à chaud pour la session : c'est vous, et vous seul, qui autorisez le chargement.
Un mod écrit de cette façon ne se charge que dans la session qui l'a créé. Pour le conserver, copiez son dossier ailleurs, dans le dépôt du projet par exemple.
Le partager avec l'équipe#
Un mod reste un plugin : il se versionne dans son manifeste et s'installe comme les autres. Selon le public visé :
- Pour vous seul : gardez le dossier dans le projet et lancez
claude --plugin-dir ./relais-sessions. - Pour l'équipe : référencez-le dans une marketplace à vous, un dépôt privé par exemple, puis chacun l'installe avec
/plugin install relais-sessions@nom-de-la-marketplace. La marketplace peut être déclarée dans les réglages du dépôt pour tous ceux qui y travaillent. - Pour toute l'organisation : un administrateur peut imposer ou restreindre les mods par les réglages gérés.
Indiquez dans le README la version de Claude Code avec laquelle vous avez testé, et continuez à développer avec --plugin-dir : une copie installée est mise en cache par version, vos modifications n'y arrivent qu'après avoir incrémenté le numéro.
Des idées d'automatisations#
- Prévenir à la fin d'un tour. Un hook
turn.completeenvoie un résumé d'une ligne aux autres sessions du même dépôt quand une tâche longue se termine. - Signaler les fichiers sensibles. Un hook
tool.callfiltré sur les outils d'édition avertit les autres sessions dès qu'un fichier partagé est modifié : schéma de base, contrat d'API, configuration. - Afficher un tableau de bord. Un panneau latéral, ouvert avec
$.ui.open, liste les sessions reliées et leurs derniers messages. - Filtrer ce qui entre. Un hook
session.receivemet en sourdine les messages d'une session trop bavarde pendant une tâche délicate.
Sécurité et limites à connaître#
Un mod n'est pas un gadget d'affichage : c'est du code qui tourne avec vos droits. Avant d'en installer un, gardez ces points en tête.
- Aucun bac à sable. Un mod peut lire et écrire vos fichiers, lancer des programmes, faire des requêtes réseau, lire vos variables d'environnement, voir chaque prompt et chaque appel d'outil, et approuver un appel d'outil avant qu'on vous pose la question. N'installez que des mods dont vous avez lu le code ou dont vous connaissez l'auteur.
- Un message reçu n'est jamais un ordre de votre part. Il ne peut pas répondre à une demande de permission, et une commande écrite dedans, comme
/compact, reste du texte. Les règles de permission de la session qui reçoit s'appliquent toujours. - Le nom de l'expéditeur est déclaratif. Il vaut ce que l'expéditeur a écrit : ne fondez aucune décision de sécurité dessus.
- Vous gardez la main sur la réception. Le réglage
crossSessionInboundaccepte trois valeurs :accept,hold(le message attend votre accord) etrefuse. Une session qui refuse les messages les écarte avant même que le mod les voie. - Du texte, rien d'autre. Ni fichiers, ni historique de conversation. Les boucles entre deux sessions sont freinées automatiquement, et une session ne met en file que 50 messages acceptés.
- L'affichage dépend de l'endroit. Les fonctions d'un mod s'exécutent partout où le plugin est chargé, mais panneaux et bandeaux ne s'affichent que dans le terminal et l'application de bureau, pas dans l'extension VS Code ni avec
claude -p. - Un interrupteur général existe.
claude --safe-modedésactive tous les mods installés pour une session ;"disableAllHooks": truedans~/.claude/settings.jsonles coupe partout.
Faut-il s'y mettre ?#
Si vous n'ouvrez qu'une session à la fois, les mods vous serviront surtout à personnaliser l'interface ou à encadrer des commandes risquées. Dès que vous faites travailler plusieurs sessions sur le même dépôt, en revanche, le gain est concret : les sessions cessent de s'ignorer, et vous arrêtez de faire le facteur entre vos terminaux.
Notre conseil : commencez par un mod qui se contente d'observer et d'afficher, comme celui de cet article, avant de lui confier des envois automatiques. Chez Websource, nous utilisons Claude Code au quotidien sur nos projets Symfony et PrestaShop ; si vous voulez mettre en place ce type d'outillage dans votre équipe, parlons-en.
Questions fréquentes#
Faut-il un mod pour envoyer un message à une autre session Claude Code ?#
Non. Claude sait déjà le faire quand vous le lui demandez, avec ses outils ListAgents et SendMessage. Le mod sert à automatiser ces échanges, à réagir aux messages reçus et à partager un état entre sessions.
Quelle version de Claude Code faut-il pour utiliser les mods ?#
La version 2.1.287 ou une version plus récente dans le terminal, et la 2.1.286 dans l'onglet Code de l'application de bureau. La commande claude --version affiche la vôtre.
Un mod peut-il joindre une session sur une autre machine ?#
Oui, à condition que les sessions soient connectées à Remote Control. Le message passe alors par les serveurs d'Anthropic, alors qu'entre deux sessions de la même machine il circule par un socket local.
Les mods sont-ils dangereux ?#
Un mod est du code qui s'exécute avec vos permissions, sans bac à sable : il peut lire vos fichiers, lancer des programmes et voir vos prompts. Il faut donc n'installer que des mods de confiance et les inspecter avec claude plugin validate.
Comment désactiver un mod ?#
Désactivez ou désinstallez son plugin depuis l'onglet Installed de /plugin. Pour couper tous les mods installés le temps d'une session, lancez Claude Code avec --safe-mode.
Article publié le 12/10/2026 sous la signature de Jonathan Gutierrez, rédigé à partir des sources citées ci-dessus, puis relu avant publication. Une information vous semble inexacte ou datée ? Signalez-le nous, nous corrigeons.







