temps-session.py 1.0.0 compte comme du travail l'attente qui précède un message reçu d'une autre session Claude Code (SendMessage), dès que cette attente dure moins de --pause (30 min par défaut).
Constat
Une session d'agent termine son tour, puis attend sans rien faire. Une autre session lui écrit par SendMessage 21 min 30 s plus tard, et l'agent reprend le travail. L'outil ajoute ces 21 min 30 s au temps actif de la séance, alors que la consigne dit : « l'attente n'est jamais comptée ».
Mesure faite sur un vrai transcript, avec le même ticket et le même jour :
--depuis
Temps proposé
avant l'attente
1 354 s
à l'arrivée du message
62 s
L'écart est de 1 292 s, soit la durée de l'attente (1 290 s) à 2 s près.
Cause
Dans le transcript, le message reçu est une entrée "type": "user" avec "isMeta": true. Son contenu commence par « Another Claude session sent a message: » et contient une balise <cross-session-message …>.
humain() (l. 58-67) rend False pour toute entrée isMeta, donc l'entrée n'ouvre pas de nouvelle plage.
plages() (l. 95-105) prolonge alors la plage en cours jusqu'à elle, puisque le trou (1 290 s) est sous --pause (1 800 s). L'attente est comptée.
Le même défaut vaut pour toute entrée user non humaine qui arrive après une attente, par exemple un avis « [Cross-session idle notice] ».
Correctif proposé
Une entrée user qui n'est pas un tool_result et qui arrive alors que l'agent avait rendu la main doit ouvrir une nouvelle plage, au même titre qu'un message de l'humain. Le temps d'attente qui la précède n'est alors pas compté.
Stories
Test d'abord, vu en échec : un transcript synthétique où un tour se termine, puis une entrée userisMeta portant <cross-session-message arrive 20 min plus tard (sous --pause). L'outil ne doit compter que les deux tours, pas le trou.
humain() ou plages() : une entrée user reçue d'une autre session (ou plus largement non tool_result) ouvre une nouvelle plage.
Test de non-régression : une entrée isMeta écrite pendant un tour (rappel système, sortie de hook) ne coupe pas la plage.
CHANGELOG.md et VERSION : version corrective 1.0.1.
Critères d'acceptation
Étant donné un tour terminé, suivi 20 min plus tard d'un message d'une autre session, quand on lance l'outil, alors ces 20 min ne sont pas comptées.
Étant donné un transcript où chaque tour s'ouvre sur un message de l'humain, quand on lance l'outil, alors le résultat est identique à celui de la version 1.0.0 (tests existants verts). Reformulé le 26/10 sur décision de Baudouin (voie (a)) ; texte d'origine : Étant donné un transcript sans message entre sessions…
L'outil reste en lecture seule : il ne lit que le transcript et l'API Gitea en GET.
*`temps-session.py` 1.0.0 compte comme du travail l'attente qui précède un message reçu d'une autre session Claude Code (`SendMessage`), dès que cette attente dure moins de `--pause` (30 min par défaut).*
## Constat
Une session d'agent termine son tour, puis attend sans rien faire. Une autre session lui écrit par `SendMessage` 21 min 30 s plus tard, et l'agent reprend le travail. L'outil ajoute ces 21 min 30 s au temps actif de la séance, alors que la consigne dit : « l'attente n'est jamais comptée ».
Mesure faite sur un vrai transcript, avec le même ticket et le même jour :
| `--depuis` | Temps proposé |
|---|---|
| avant l'attente | 1 354 s |
| à l'arrivée du message | 62 s |
L'écart est de 1 292 s, soit la durée de l'attente (1 290 s) à 2 s près.
## Cause
- Dans le transcript, le message reçu est une entrée `"type": "user"` avec `"isMeta": true`. Son contenu commence par « Another Claude session sent a message: » et contient une balise `<cross-session-message …>`.
- `humain()` (l. 58-67) rend `False` pour toute entrée `isMeta`, donc l'entrée n'ouvre pas de nouvelle plage.
- `plages()` (l. 95-105) prolonge alors la plage en cours jusqu'à elle, puisque le trou (1 290 s) est sous `--pause` (1 800 s). L'attente est comptée.
- Le même défaut vaut pour toute entrée `user` non humaine qui arrive après une attente, par exemple un avis « [Cross-session idle notice] ».
## Correctif proposé
Une entrée `user` qui n'est pas un `tool_result` et qui arrive alors que l'agent avait rendu la main doit ouvrir une nouvelle plage, au même titre qu'un message de l'humain. Le temps d'attente qui la précède n'est alors pas compté.
## Stories
- [x] Test d'abord, vu en échec : un transcript synthétique où un tour se termine, puis une entrée `user` `isMeta` portant `<cross-session-message` arrive 20 min plus tard (sous `--pause`). L'outil ne doit compter que les deux tours, pas le trou.
- [x] `humain()` ou `plages()` : une entrée `user` reçue d'une autre session (ou plus largement non `tool_result`) ouvre une nouvelle plage.
- [x] Test de non-régression : une entrée `isMeta` écrite *pendant* un tour (rappel système, sortie de hook) ne coupe pas la plage.
- [x] `CHANGELOG.md` et `VERSION` : version corrective 1.0.1.
## Critères d'acceptation
- [x] *Étant donné* un tour terminé, suivi 20 min plus tard d'un message d'une autre session, *quand* on lance l'outil, *alors* ces 20 min ne sont pas comptées.
- [x] *Étant donné* un transcript où chaque tour s'ouvre sur un message de l'humain, *quand* on lance l'outil, *alors* le résultat est identique à celui de la version 1.0.0 (tests existants verts).
*Reformulé le 26/10 sur décision de Baudouin (voie (a)) ; texte d'origine : ~~Étant donné un transcript sans message entre sessions…~~*
- [x] L'outil reste en lecture seule : il ne lit que le transcript et l'API Gitea en GET.
Gile
added the bug label 2026-10-24 18:28:30 +02:00
Stories : les 4 sont faites. Le test a été vu en échec avant le correctif (1620 s au lieu de 420 s).
Critères 2 et 3 cochés :
les tests 1 à 12 de la 1.0.0 restent verts ;
le diff n'ajoute ni écriture ni appel réseau.
Critère 1 : il est prouvé par les tests 14 et 15 sur la branche, et l'attente de 1 290 s de ce ticket disparaît sur le vrai transcript. Je ne le coche qu'après fusion, sur l'outil publié au tag v1.0.1, que Baudouin pose.
Décision : la fin de tour se lit sur stop_reason (deuxième solution, retenue par Baudouin), et non sur la balise <cross-session-message.
Écart avec le texte de la story : VERSION n'existait pas, il est créé à la demande de Baudouin. Il doit rester égal à la constante de l'outil, et un test le vérifie.
PR #3 vers `develop` (Refs #2).
- Stories : les 4 sont faites. Le test a été vu en échec avant le correctif (1620 s au lieu de 420 s).
- Critères 2 et 3 cochés :
- les tests 1 à 12 de la 1.0.0 restent verts ;
- le diff n'ajoute ni écriture ni appel réseau.
- Critère 1 : il est prouvé par les tests 14 et 15 sur la branche, et l'attente de 1 290 s de ce ticket disparaît sur le vrai transcript. Je ne le coche qu'après fusion, sur l'outil publié au tag `v1.0.1`, que Baudouin pose.
- Décision : la fin de tour se lit sur `stop_reason` (deuxième solution, retenue par Baudouin), et non sur la balise `<cross-session-message`.
- Écart avec le texte de la story : `VERSION` n'existait pas, il est créé à la demande de Baudouin. Il doit rester égal à la constante de l'outil, et un test le vérifie.
Correction : je décoche le critère 2, je l'avais coché à tort.
Les tests existants restent verts.
Mais sur 36 transcripts réels, trois transcripts sans aucun message entre sessions changent aussi : l'attente qui précède un /rename (243 s) ou une commande slash tapée après une fin de tour (583 s et 210 s) n'est plus comptée.
C'est une attente de l'humain, donc la consigne (« l'attente n'est jamais comptée ») est mieux respectée, mais le résultat n'est pas « identique à la 1.0.0 ».
À trancher par Baudouin :
(a) accepter cet élargissement et reformuler le critère 2 en « identique pour un transcript dont chaque tour s'ouvre sur un message de l'humain » ;
(b) limiter le correctif aux entrées qui portent <cross-session-message ou [Cross-session, ce qui revient à la première solution, écartée.
Correction : je **décoche le critère 2**, je l'avais coché à tort.
- Les tests existants restent verts.
- Mais sur 36 transcripts réels, trois transcripts **sans aucun** message entre sessions changent aussi : l'attente qui précède un `/rename` (243 s) ou une commande slash tapée après une fin de tour (583 s et 210 s) n'est plus comptée.
- C'est une attente de l'humain, donc la consigne (« l'attente n'est jamais comptée ») est mieux respectée, mais le résultat n'est pas « identique à la 1.0.0 ».
À trancher par Baudouin :
- (a) accepter cet élargissement et reformuler le critère 2 en « identique pour un transcript dont chaque tour s'ouvre sur un message de l'humain » ;
- (b) limiter le correctif aux entrées qui portent `<cross-session-message` ou `[Cross-session`, ce qui revient à la première solution, écartée.
Preuve du critère 1 sur le code fusionné dans develop (2a6f0e6). J'ai rejoué le transcript réel décrit dans ce ticket, de la fin du tour jusqu'à 3 min après le message de l'autre session :
Version
Temps compté
1.0.0
1 324 s
1.0.1
34 s
L'écart est de 1 290 s, soit exactement l'attente avant le message (1 290,2 s). Les tests du commit fusionné passent (ok : outil de saisie des temps).
Il reste :
critère 1 : je le coche une fois v1.0.1 publié sur main (PR de publication ouverte) ;
critère 2 : il attend la décision (a) ou (b) de Baudouin (commentaire précédent).
Preuve du critère 1 sur le code fusionné dans `develop` (`2a6f0e6`). J'ai rejoué le transcript réel décrit dans ce ticket, de la fin du tour jusqu'à 3 min après le message de l'autre session :
| Version | Temps compté |
|---|---|
| 1.0.0 | 1 324 s |
| 1.0.1 | 34 s |
L'écart est de 1 290 s, soit exactement l'attente avant le message (1 290,2 s). Les tests du commit fusionné passent (`ok : outil de saisie des temps`).
Il reste :
- **critère 1** : je le coche une fois `v1.0.1` publié sur `main` (PR de publication ouverte) ;
- **critère 2** : il attend la décision (a) ou (b) de Baudouin (commentaire précédent).
Critère 1 coché. raw/tag/v1.0.1/outils/temps-session.py affiche temps-session.py 1.0.1 et il est identique, octet pour octet (cmp), au code fusionné 2a6f0e6. C'est ce code qui donne 34 s au lieu de 1 324 s dans la mesure précédente.
Reste le critère 2, qui attend la décision de Baudouin.
Critère 1 coché. `raw/tag/v1.0.1/outils/temps-session.py` affiche `temps-session.py 1.0.1` et il est identique, octet pour octet (`cmp`), au code fusionné `2a6f0e6`. C'est ce code qui donne 34 s au lieu de 1 324 s dans la mesure précédente.
Reste le critère 2, qui attend la décision de Baudouin.
Décision de Baudouin, 26/10 : critère 2, voie (a).
La 1.0.1 reste telle quelle. Toute entrée user arrivée après la fin d'un tour de l'agent ouvre une nouvelle plage : message ou avis d'une autre session, mais aussi /rename ou commande slash. L'attente qui la précède n'est plus comptée, conformément à la consigne « l'attente n'est jamais comptée ».
Le critère 2 est reformulé dans la description : « Étant donné un transcript où chaque tour s'ouvre sur un message de l'humain, quand on lance l'outil, alors le résultat est identique à celui de la version 1.0.0 (tests existants verts). »
Preuve :
les tests 1 à 12 de la 1.0.0 sont verts sur v1.0.1 (2a6f0e6) ;
dans ces tests, chaque tour s'ouvre sur un message de l'humain.
Tous les critères sont cochés, je ferme le ticket.
(Écrit par george-claude à la demande de Baudouin.)
**Décision de Baudouin, 26/10 : critère 2, voie (a).**
La 1.0.1 reste telle quelle. Toute entrée `user` arrivée après la fin d'un tour de l'agent ouvre une nouvelle plage : message ou avis d'une autre session, mais aussi `/rename` ou commande slash. L'attente qui la précède n'est plus comptée, conformément à la consigne « l'attente n'est jamais comptée ».
Le critère 2 est reformulé dans la description : « Étant donné un transcript où chaque tour s'ouvre sur un message de l'humain, quand on lance l'outil, alors le résultat est identique à celui de la version 1.0.0 (tests existants verts). »
Preuve :
- les tests 1 à 12 de la 1.0.0 sont verts sur `v1.0.1` (`2a6f0e6`) ;
- dans ces tests, chaque tour s'ouvre sur un message de l'humain.
Tous les critères sont cochés, je ferme le ticket.
*(Écrit par george-claude à la demande de Baudouin.)*
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
temps-session.py1.0.0 compte comme du travail l'attente qui précède un message reçu d'une autre session Claude Code (SendMessage), dès que cette attente dure moins de--pause(30 min par défaut).Constat
Une session d'agent termine son tour, puis attend sans rien faire. Une autre session lui écrit par
SendMessage21 min 30 s plus tard, et l'agent reprend le travail. L'outil ajoute ces 21 min 30 s au temps actif de la séance, alors que la consigne dit : « l'attente n'est jamais comptée ».Mesure faite sur un vrai transcript, avec le même ticket et le même jour :
--depuisL'écart est de 1 292 s, soit la durée de l'attente (1 290 s) à 2 s près.
Cause
"type": "user"avec"isMeta": true. Son contenu commence par « Another Claude session sent a message: » et contient une balise<cross-session-message …>.humain()(l. 58-67) rendFalsepour toute entréeisMeta, donc l'entrée n'ouvre pas de nouvelle plage.plages()(l. 95-105) prolonge alors la plage en cours jusqu'à elle, puisque le trou (1 290 s) est sous--pause(1 800 s). L'attente est comptée.usernon humaine qui arrive après une attente, par exemple un avis « [Cross-session idle notice] ».Correctif proposé
Une entrée
userqui n'est pas untool_resultet qui arrive alors que l'agent avait rendu la main doit ouvrir une nouvelle plage, au même titre qu'un message de l'humain. Le temps d'attente qui la précède n'est alors pas compté.Stories
userisMetaportant<cross-session-messagearrive 20 min plus tard (sous--pause). L'outil ne doit compter que les deux tours, pas le trou.humain()ouplages(): une entréeuserreçue d'une autre session (ou plus largement nontool_result) ouvre une nouvelle plage.isMetaécrite pendant un tour (rappel système, sortie de hook) ne coupe pas la plage.CHANGELOG.mdetVERSION: version corrective 1.0.1.Critères d'acceptation
Reformulé le 26/10 sur décision de Baudouin (voie (a)) ; texte d'origine :
Étant donné un transcript sans message entre sessions…PR #3 vers
develop(Refs #2).v1.0.1, que Baudouin pose.stop_reason(deuxième solution, retenue par Baudouin), et non sur la balise<cross-session-message.VERSIONn'existait pas, il est créé à la demande de Baudouin. Il doit rester égal à la constante de l'outil, et un test le vérifie.Correction : je décoche le critère 2, je l'avais coché à tort.
/rename(243 s) ou une commande slash tapée après une fin de tour (583 s et 210 s) n'est plus comptée.À trancher par Baudouin :
<cross-session-messageou[Cross-session, ce qui revient à la première solution, écartée.Preuve du critère 1 sur le code fusionné dans
develop(2a6f0e6). J'ai rejoué le transcript réel décrit dans ce ticket, de la fin du tour jusqu'à 3 min après le message de l'autre session :L'écart est de 1 290 s, soit exactement l'attente avant le message (1 290,2 s). Les tests du commit fusionné passent (
ok : outil de saisie des temps).Il reste :
v1.0.1publié surmain(PR de publication ouverte) ;Critère 1 coché.
raw/tag/v1.0.1/outils/temps-session.pyaffichetemps-session.py 1.0.1et il est identique, octet pour octet (cmp), au code fusionné2a6f0e6. C'est ce code qui donne 34 s au lieu de 1 324 s dans la mesure précédente.Reste le critère 2, qui attend la décision de Baudouin.
Décision de Baudouin, 26/10 : critère 2, voie (a).
La 1.0.1 reste telle quelle. Toute entrée
userarrivée après la fin d'un tour de l'agent ouvre une nouvelle plage : message ou avis d'une autre session, mais aussi/renameou commande slash. L'attente qui la précède n'est plus comptée, conformément à la consigne « l'attente n'est jamais comptée ».Le critère 2 est reformulé dans la description : « Étant donné un transcript où chaque tour s'ouvre sur un message de l'humain, quand on lance l'outil, alors le résultat est identique à celui de la version 1.0.0 (tests existants verts). »
Preuve :
v1.0.1(2a6f0e6) ;Tous les critères sont cochés, je ferme le ticket.
(Écrit par george-claude à la demande de Baudouin.)