--- title: GitHub-Odoo Sync url: https://blog.guigpap.com/fr/workflows/github-odoo-sync/ url_md: https://blog.guigpap.com/fr/workflows/github-odoo-sync.md category: automation date: '2026-01-20' maturite: production techno: - github - odoo - n8n application: - automation - business --- # GitHub-Odoo Sync > Synchronisation bidirectionnelle issues/commits GitHub avec Odoo via hub N8N et 7 sous-workflows spécialisés ## 1. Quoi ? — Définition et contexte Le workflow **GitHub-Odoo Sync** transforme chaque événement GitHub (issue, PR, commit) en une opération Odoo correspondante : tâche projet créée, label appliqué, milestone synchronisé, commit historisé. Depuis le refactoring #274, l'architecture est passée d'un workflow monolithique de 138 nodes à un **hub de 13 nodes qui dispatche vers 7 sous-workflows spécialisés**. > **Note - Pont Dev ↔ Business** > > GitHub est l'outil naturel pour les développeurs (issues, PRs, commits). Odoo est l'outil naturel pour la gestion business (projets, timesheets, CRM). Ce workflow fait le pont sans imposer aux uns d'apprendre l'outil des autres. ### Architecture hub-spoke | Workflow | ID | Nodes | Rôle | |----------|------|-------|------| | **GitHub Project Sync** (parent) | `JR8ESduxKNSQ7iVs` | 13 | Trigger + lookup mapping + dispatch par event type | | **SW-1 Issue Lifecycle** | `8yU07chLmZuogA3j` | 23 | opened / closed / reopened + sub-issues parent | | **SW-2 Issue Labels** | `OMeuYvh0B9XA2S1H` | 11 | labeled / unlabeled | | **SW-3 Issue Properties** | `0BoSdwR4d8S9Fq0C` | 32 | assigned / edited / pinned / renamed / deleted | | **SW-4 Issue Milestone** | `EZiZqgZNpxEzof7F` | 13 | milestoned / demilestoned | | **SW-5 PR Handler** | `3o7IzU2brzJeedB7` | 18 | pull_request opened / merged / closed | | **SW-6 Git Events** | `O2jirkOJOpOIy5ez` | 18 | push (commits) + create (branch stage transition) | | **SW-7 Repo Entity** | `WVWY01qmw9uYzSl8` | 28 | Label + Milestone CRUD au niveau du repo | ### Dépendance partagée Un sous-workflow utilitaire `odoo-get-or-create-tag` (`j7e2EzEtIJ42T2S4`) est appelé par SW-1, SW-2 et SW-7 pour créer ou récupérer des tags Odoo correspondant aux labels GitHub. ### Architecture visuelle ```mermaid flowchart TD GH["GitHub · webhooks"] Mapping["Data Table · github_project_mapping"] subgraph Hub["GitHub Project Sync · 13 nodes"] direction TB Trigger["Github Trigger"] Lookup["Lookup Mapping"] SyncEnabled["Sync Enabled?"] Switch["Switch · Event Type"] end subgraph SubsIssues["Issues sub-workflows"] direction TB SW1["SW-1 Lifecycle · 23n"] SW2["SW-2 Labels · 11n"] SW3["SW-3 Properties · 32n"] SW4["SW-4 Milestone · 13n"] end subgraph SubsOther["Autres sub-workflows"] direction TB SW5["SW-5 PR Handler · 18n"] SW6["SW-6 Git Events · 18n"] SW7["SW-7 Repo Entity · 28n"] end TagSW["odoo-get-or-create-tag"] Odoo["Odoo 18 · guig_db"] GH --> Trigger Mapping --> Lookup Trigger --> Lookup --> SyncEnabled --> Switch Switch --> SW1 Switch --> SW2 Switch --> SW3 Switch --> SW4 Switch --> SW5 Switch --> SW6 Switch --> SW7 SW1 --> TagSW SW2 --> TagSW SW7 --> TagSW SW1 --> Odoo SW2 --> Odoo SW3 --> Odoo SW4 --> Odoo SW5 --> Odoo SW6 --> Odoo SW7 --> Odoo TagSW --> Odoo ``` ### Événements supportés Tous les événements GitHub utiles à un workflow projet sont synchronisés : | Catégorie | Événements | Sous-workflow | |-----------|------------|---------------| | Cycle de vie issue | opened, closed, reopened | SW-1 | | Sub-issues | parent_added, parent_removed | SW-1 | | Labels issue | labeled, unlabeled | SW-2 | | Propriétés issue | assigned, edited, pinned, renamed, deleted | SW-3 | | Milestone issue | milestoned, demilestoned | SW-4 | | Pull requests | opened, closed, merged | SW-5 | | Push & branches | push (commits), create (branch) | SW-6 | | Repo-level | label/milestone CRUD | SW-7 | --- ## 2. Pourquoi ? — Enjeux et motivations ### Le problème sans synchro | Sans sync | Conséquence | |-----------|-------------| | **Visibilité métier** | Les issues restent invisibles depuis Odoo | | **Suivi du temps** | Impossible de lier une heure de timesheet à une issue précise | | **Historique commits** | Dispersé dans GitHub, pas de vue cumulée par tâche Odoo | | **Double saisie** | Créer une tâche Odoo manuellement pour chaque issue | ### Pourquoi un hub-spoke plutôt qu'un workflow unique ? Le workflow d'origine (138 nodes) avait grossi au fil des événements supportés. Trois symptômes ont déclenché le refactoring #274 : | Symptôme | Cause | Conséquence | |----------|-------|-------------| | **Édition lente** | UI N8N peine à charger 138 nodes | Modifs ergonomiquement coûteuses | | **Tests rébarbatifs** | Touche un sub-flow → re-tester le tout | Régressions silencieuses | | **Couplage** | Code IA tag, Odoo task, milestone partagés | Changement local cassait des branches lointaines | L'architecture en hub avec 7 sous-workflows résout ces tensions : chaque SW a un périmètre étroit, peut être édité et testé isolément, et signe son contrat d'I/O via Execute Workflow Trigger. ### Pourquoi GitHub → Odoo et pas l'inverse ? | Critère | Choix | |---------|-------| | **Source de vérité du code** | GitHub (là où vivent les commits, les PRs, les CI) | | **Source de vérité business** | Odoo (timesheets, factures, projets clients) | | **Direction du déclenchement** | GitHub webhooks (temps réel, sub-second) | | **Enrichissement** | Odoo ajoute le contexte business (projet, tags, complexité, estimation) | La direction inverse (fermer une issue depuis Odoo) est techniquement faisable mais ajoute un risque de boucle. Pour le moment, GitHub reste source ; Odoo est sink + dashboard. --- ## 3. Comment ? — Mise en œuvre technique ### Le parcours d'un événement **1. Webhook GitHub** — Le trigger N8N reçoit l'événement avec sa signature HMAC `X-Hub-Signature-256` (vérifiée par N8N). **2. Lookup mapping** — Le hub consulte la Data Table `github_project_mapping` pour trouver le projet Odoo associé au repo (`owner/repo` → `odoo_project_id`). **3. Switch event type** — Le hub route vers le sous-workflow adapté selon `headers.x-github-event` et `body.action`. **4. Sub-workflow execution** — Chaque SW reçoit en passthrough `{body, headers, query, mapping}` et applique sa logique métier. **5. Appels Odoo XML-RPC** — Tous les SWs convergent vers le même endpoint Odoo : `http://odoo:8069/xmlrpc/2/object`, base de données `guig_db`. > **Caution - DB name = guig_db** > > La base Odoo s'appelle **`guig_db`**, pas `odoo`. Toutes les requêtes XML-RPC utilisent ce nom — toute confusion produit une erreur silencieuse `Database does not exist`. ### Module Odoo project_github_sync L'addon custom `project_github_sync` (v18.0.5.6.0) ajoute les champs nécessaires à la synchro. **Tous les champs custom utilisent le préfixe `x_`** pour ne pas entrer en collision avec les natifs Odoo : #### Champs sur `project.task` | Famille | Champ | Type | Source | |---------|-------|------|--------| | GitHub | `x_github_issue_id` | Integer (indexé) | numéro issue/PR | | GitHub | `x_github_url` | Char(500) | URL GitHub | | GitHub | `x_github_repo` | Char (indexé) | `owner/repo` | | GitHub | `x_github_milestone_id` | Integer | numéro milestone | | GitHub | `x_github_parent_issue_id` | Integer | parent (sub-issues) | | GitHub | `x_github_commit_ids` | One2many | commits liés | | Effort (#188) | `x_estimated_hours` | Float(10,2) | estimation à la création | | Effort (#188) | `x_complexity` | Selection | trivial / simple / moderate / complex | | AI triage (#296) | `x_ai_project_triaged_at` | Datetime | dernière analyse projet (TTL 7j) | | AI triage (#296) | `x_ai_personal_triaged_at` | Datetime | dernière analyse perso (TTL 7j) | | Telemetry | `x_claude_time_total` | Float(10,2) | temps actif Claude Code | | Telemetry | `x_claude_cost_total` | Float(10,4) | coût API USD | | Telemetry | `x_claude_token_total` | Integer | tokens cumulés | | Telemetry | `x_claude_sessions` | Integer | nombre de sessions | | Telemetry | `x_claude_lines_added/removed` | Integer | LOC delta | | Telemetry | `x_claude_session_ids` | One2many | historique sessions | | Telemetry | `x_claude_category` | Selection | catégorie auto pour tâches génériques | #### Modèles ajoutés | Modèle | Rôle | |--------|------| | `project.task.github.commit` | Un enregistrement par commit lié à une tâche (sha, message, auteur, date) | | `project.task.claude.session` | Un enregistrement par session Claude Code (id, durée, coût, tldr, category) | ### Issue Lifecycle (SW-1) en détail Le sous-workflow le plus chargé est `SW-1 Issue Lifecycle`, qui gère 5 actions distinctes : ```mermaid flowchart TD Trigger["Execute Workflow Trigger · passthrough"] Switch["Switch · body.action"] subgraph Opened["opened"] direction TB Prep["Prepare Odoo Data + complexity + estimate"] MD["Convert MD to HTML"] Area["Detect Area Tag · area:xxx, feat(xxx)"] Tag["Get/Create Tag"] Create["Create Task · stage Backlog"] end subgraph Closed["closed"] direction TB PrepC["Prepare Closed Data"] Find1["Find Task by x_github_issue_id"] IsProj["Is Project Task?"] CloseProj["Close Project Task · stage 25 Done"] ClosePriv["Close Private Task · personal_stage 6 Terminé"] end subgraph Reopened["reopened"] direction TB PrepR["Prepare Reopened Data"] Find2["Find Task"] ReopenProj["Reopen → default_stage_id"] ReopenPriv["Reopen Private → personal_stage 2 Aujourd'hui"] end subgraph SubLink["parent_added · parent_removed"] direction TB PrepL["Prepare Link/Unlink"] FindBoth["Find Sub + Parent Tasks"] Link["Set parent_id + x_github_parent_issue_id"] Unlink["Clear parent_id"] end Trigger --> Switch Switch --> Prep --> MD --> Area --> Tag --> Create Switch --> PrepC --> Find1 --> IsProj IsProj --> CloseProj IsProj --> ClosePriv Switch --> PrepR --> Find2 Find2 --> ReopenProj Find2 --> ReopenPriv Switch --> PrepL --> FindBoth FindBoth --> Link FindBoth --> Unlink ``` ### Estimation d'effort à la création (#188) Quand une nouvelle issue est créée, SW-1 dérive automatiquement deux champs depuis le titre + body : | Champ | Calcul | |-------|--------| | `x_complexity` | Heuristique sur la longueur du body, mots-clés (refactor, migration, multi-step) | | `x_estimated_hours` | Modèle déterministe basé sur la cohorte de complexité (cf. [Effort Estimator V1](/fr/workflows/monitoring-digests/)) | L'estimation initiale est une borne basse qui sert de référence. Le champ `effective_hours` (timesheets agrégés) permet ensuite de mesurer l'écart estimation vs réalité, alimentant un weekly coverage report. ### Daily GitHub Triage + AI double-triage En parallèle de la synchro événementielle, un workflow planifié quotidien (`61uBaaFQssxT1HJ0`, 25 nodes) effectue un rattrapage et un triage AI : | Étape | Action | |-------|--------| | 1. Sync de rattrapage | Récupère les issues GitHub modifiées depuis 24h, met à jour Odoo | | 2. Stage promotion | Si une issue est `in progress` côté GitHub mais `Backlog` côté Odoo → promu à `In Progress` | | 3. AI Issue Triage (#296) | Sub-workflow `AwqOyyAC3sUg9tSm` (12n) — codex-yolo analyse l'issue et propose stage projet + horizon perso | | 4. Cache TTL 7j | Les champs `x_ai_*_triaged_at` empêchent de re-triager une issue déjà analysée récemment | Le triage AI est aussi exposé en mode interactif via l'API `triage-interactive-api` consommée par Claude Code lors d'une session ouverte (commande `/triage`). ### Format des données passées Le hub passe à chaque sous-workflow un objet uniforme : ```json { "body": { /* GitHub webhook payload */ }, "headers": { "x-github-event": "issues", "x-github-delivery": "..." }, "query": {}, "mapping": { "github_repo": "owner/repo", "odoo_project_id": 5, "default_stage_id": 21, "sync_enabled": true } } ``` Le contrat d'entrée est strict (Execute Workflow Trigger en mode `passthrough` désactivé pour SW-3 à SW-7 qui utilisent des `workflowInputs` typés). ### Initial Sync (#50) Pour intégrer un repo existant qui contient déjà des centaines d'issues, le workflow `Initial Sync` (#50) permet une importation one-shot : pagination GitHub API + création séquentielle des tâches Odoo + setup des tags. À utiliser uniquement à la mise en place ; ensuite, le sync événementiel suffit. --- ## 4. Et si ? — Perspectives et limites ### Limites actuelles | Limite | Impact | Mitigation | |--------|--------|------------| | **Unidirectionnel** | Fermer côté Odoo ne ferme pas l'issue GitHub | Sync inverse à concevoir avec flag anti-boucle | | **Mapping manuel** | Chaque repo doit être ajouté à la Data Table | Auto-discovery par organisation à terme | | **Estimation initiale = borne basse** | Issues complexes sous-estimées | Recalibrage hebdomadaire prévu via cohort matching | | **Triage AI 24h de retard max** | Re-triage le lendemain si issue ignorée | Acceptable, le triage est un assistant pas un oracle | ### Scénarios d'évolution **Si besoin de sync bidirectionnelle** : - Webhook Odoo `automation.action` quand une tâche passe en stage `Done` - Le webhook fait un commentaire GitHub `Closed via Odoo task #N` - Flag `synced_from_odoo` dans la table de mapping pour bloquer la rétro-propagation **Si beaucoup de repos à gérer** : - Auto-discovery des repos d'une organisation GitHub - Templates de mapping par convention de nom (`*-backend` → projet Backend) - UI Odoo dédiée pour configurer les mappings sans toucher la Data Table **Si le triage AI devient critique** : - Activer un mode confiance haute (auto-set du stage si `confidence > 0.9`) - Logger les décisions dans `claude_code_active_sessions` pour audit - Permettre un override manuel via `/triage ` --- ## Pages liées ### Infrastructure - [Odoo 18 sur Docker](/fr/infrastructure/odoo-18-setup/) — Module project_github_sync, champs x_* - [Pourquoi Odoo](/fr/infrastructure/why-odoo/) — Choix et architecture - [N8N en mode Queue](/fr/infrastructure/n8n-queue-mode/) — Backend qui exécute les sub-workflows ### Workflows - [Claude Code Telemetry](/fr/workflows/claude-code-telemetry/) — Alimente les champs x_claude_* - [Monitoring Digests](/fr/workflows/monitoring-digests/) — Effort Estimator V1, Daily Todo Digest, Daily GitHub Triage - [Error Handler](/fr/workflows/error-handler/) — Capture les erreurs des 7 sub-workflows ### Référence - [Glossaire](/fr/reference/glossary/) — XML-RPC, Webhook, HMAC, Sub-workflow ## Metadonnees agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0