TO

tanim913/odoo-migrate-to-20

Developer tools
12 stars Quality 70 Trend 33MIT

Agent skill: migrate Odoo 14–19 modules to Odoo 20 and prove they install

Overview

is an agent skill that migrates a custom Odoo module (or a whole addons folder) from , then proves the result . It installs the module on a throwaway database and clicks through it in a real browser; getting the code to compile is not where it stops. It works with any coding agent that can read files and run shell commands. - The scanner knows 100+ breaking changes, and Odoo 20 breaks some code without an error: server t-esc renders nothing, _sql_constraints are dropped, name_get is never called, oe_chatter disappears, and overrides with an old signature crash only when called. The scanner counts these as seriously as install errors. - The module is copied into a separate Odoo 20 tree, with one git commit per phase, so every change is a diff you can review. Odoo core is checked for changes after every automatic pass. - A renamed copy of an Odoo module is rebuilt from the upstream Odoo 20 code, keeping your model names and only your real changes.

README

odoo-migrate-to-20 is an agent skill that migrates a custom Odoo module (or a whole addons folder) from Odoo 14, 15, 16, 17, 18 or 19 to Odoo 20, then proves the result installs and runs. It installs the module on a throwaway database and clicks through it in a real browser; getting the code to compile is not where it stops. It works with any coding agent that can read files and run shell commands.

  • Nothing silent gets through. The scanner knows 100+ breaking changes, and Odoo 20 breaks some code without an error: server t-esc renders nothing, _sql_constraints are dropped, name_get is never called, oe_chatter disappears, and overrides with an old signature crash only when called. The scanner counts these as seriously as install errors.
  • Your original is never touched. The module is copied into a separate Odoo 20 tree, with one git commit per phase, so every change is a diff you can review. Odoo core is checked for changes after every automatic pass.
  • Forks are rebased, not ported. A renamed copy of an Odoo module is rebuilt from the upstream Odoo 20 code, keeping your model names and only your real changes.

Install in 3 steps

1. Install the skill. Run this, then pick your coding agent when it asks:

npx skills add tanim913/odoo-migrate-to-20 -g

No Node.js? Use the included installer instead:

git clone https://github.com/tanim913/odoo-migrate-to-20.git
cd odoo-migrate-to-20
./install.sh --for all --project ~/work/my-odoo-repo    # or e.g. --for cursor,claude

install.sh also adds the /odoo-migrate-to-20 command to Cursor, Antigravity, Copilot and Gemini CLI.

2. Check your machine. Run this once; it must end with READY:

~/.agents/skills/odoo-migrate-to-20/scripts/doctor.sh

If it says NOT READY, its FAIL lines tell you what is missing: usually an Odoo 20 checkout, Python 3.12+ or PostgreSQL 16+ (see Requirements).

3. Use it. In your coding agent, type:

/odoo-migrate-to-20 ~/work/odoo16/addons/my_module

or just ask: “migrate my_module from 16 to 20”.

Works in your agent

The skill is written in the open Agent Skills format: a SKILL.md with scripts and references. install.sh keeps one canonical copy in ~/.agents/skills/odoo-migrate-to-20. Skill-aware agents link to it; tools that only understand rules or commands get a small adapter that points to it. An update in one place reaches all of them.

Agent --for What gets installed How you start it
Claude Code claude ~/.claude/skills/… (+ /.claude/skills with --project-skills) /odoo-migrate-to-20 … or ask
OpenAI Codex codex ~/.codex/skills/… ask “migrate X to Odoo 20”
Cursor cursor .cursor/rules/odoo-migrate-to-20.mdc + .cursor/commands/odoo-migrate-to-20.md (user and project) /odoo-migrate-to-20 in Agent chat, or ask
Google Antigravity antigravity /.antigravity/skills/odoo-migrate-to-20.md + /.agent/workflows/odoo-migrate-to-20.md the /odoo-migrate-to-20 workflow, or ask
Gemini CLI gemini ~/.gemini/commands/odoo-migrate-to-20.toml (+ project) /odoo-migrate-to-20
GitHub Copilot copilot ~/.copilot/skills/… + /.github/prompts/odoo-migrate-to-20.prompt.md /odoo-migrate-to-20 in Copilot Chat (agent mode)
OpenCode and other skill readers agents ~/.agents/skills/odoo-migrate-to-20 (+ /.agents/skills with --project-skills) picked up automatically
Anything that reads AGENTS.md agents-md a marked section in /AGENTS.md ask

The route

Every stop has a gate, and the route doesn’t move on until the gate is met:

  1. Doctor (doctor.sh) checks Odoo 20, Python 3.12+, PostgreSQL 16+ and Chrome. Migration starts only at READY, or when you accept what is missing (for example, no enterprise).
  2. Prepare (prepare.sh) copies the module to $MIG20_TARGET_ROOT//, detects the source version, fingerprints the original and commits a baseline. fork_check.py decides whether the module is a renamed fork (see below).
  3. upgrade_code (run_upgrade_code.sh) runs Odoo 20’s own rewrite scripts safely: one script at a time, module only, never through odoo-bin, owl3-migration included. It fails if Odoo core or enterprise changed.
  4. Scan (scan.py) checks 100+ known 14→20 breaking changes and reports each hit with file:line, sorted by severity: BLOCKER, SILENT, RUNTIME, WARN. Target: 0 BLOCKER, 0 SILENT.
  5. Rewrite: renames that cross Python, XML and JS go first, then each area (Python and controllers, views and security, backend JS, website) is migrated against the references. Agents that can run parallel sub-agents use them for the JS and website areas; others do the areas one after another.
  6. Install loop (install_test.sh) installs on a throwaway database (mig20_*) until RESULT: CLEAN, runs the module’s own tests, then drops the database.
  7. Smoke test (smoke_test.py) runs Odoo’s clickbot over the module’s apps, opens every other menu action and its “new” form, and every website page as public and admin, in headless Chrome. Then a short flow tour of the module’s main flow proves the migrated code works, not just loads. Any browser console error fails the run, and the screenshots of a failure are handed to the agent.
  8. Review and report: a code review of the whole diff (using Odoo’s odoo-review skill when available), then MIGRATION_20.md with the decisions, what was not migrated, security findings, data-migration notes and what to check by hand.

What it catches

Area Examples
Manifest a version format that silently makes the module uninstallable, removed keys, removed or merged dependencies, missing files, external Python libraries
Python / ORM odoo.osv removal, read_group, name_get, _sql_constraints, @api.model create, the access-check API, odoo.http submodules, renamed fields, override signatures against Odoo 20, methods that now require an identity check
Security ir.model.access + ir.rule → ir.access, res.groups privileges, public-access review, leaked credentials in source
Views / QWeb attrs/states, tree → list, kanban-box → card, chatter, search view schema, server t-esc/t-raw (render nothing in 20), t-call parameters, report layouts, mail templates, renamed xmlids
Backend JS Owl 2 → Owl 3 (useProps, proxy, signal.ref, template this.), legacy widgets, three-argument patch, services, widget renames, asset bundles
Website publicWidget → Interactions, jQuery removal, frontend RPC, snippet options → html_builder, website_sale template and route changes, portal entries
Styling Bootstrap 4 → 5, Font Awesome → Odoo’s oi icon font
Tests and tours Odoo 20’s strict tour schema (extra_trigger, position, edition, test: true…), old run helpers, jQuery-only selectors, tour.register, steps that relied on the pre-18 implicit click, phantom_js, QUnit

Beyond regular expressions, scan.py:

  • imports every odoo.* import for real on Odoo 20, so moved helpers and renamed classes are caught. A failing import inside try/except ImportError is flagged as silent, because on 20 the fallback branch would always run;
  • resolves every JS import to a file in Odoo 20 and checks each imported name is exported. One missing file breaks the whole asset bundle;
  • imports every third-party library with your Odoo 20 Python;
  • compares every override of a core method with its Odoo 20 signature;
  • checks every name used through an Odoo module (fields.X, models.X, api.X, …) exists in Odoo 20, not only the import lines (fields.datetime, models.NewId were removed);
  • checks every external xmlid the module references against Odoo 20;
  • finds methods defined twice in one class (a rename can make one override replace another), _login overrides that drop super(), old group_expand signatures, computes that only assign self._origin, and views written under another module’s XML ID (which break -u of that module);
  • looks for secrets.

How it proves the result

  • A real install, not a syntax check, on a throwaway PostgreSQL 16 database, repeated until RESULT: CLEAN, with the module’s own tests.
  • A real browser. Odoo’s clickbot and the module’s website pages run in headless Chrome, and one console error fails the run. Odoo skips browser tests without Chrome or websocket-client; doctor.sh warns, and install_test.sh reports it as a problem, so a skipped run is never mistaken for a pass.
  • A real flow. A short tour of the module’s main flow (create and save the main record, submit the website form…) proves the migrated widgets and interactions work. When a browser test fails, the failing step and the screenshot Odoo took at that moment go straight to the agent.
  • Control runs. When a failure looks unrelated to your code, the same smoke test runs on the upstream or core module alone. A failure that reproduces there is recorded, not blamed on the migration.
  • Proof of no side effects. The original module’s fingerprint is compared at the end, core must have no git changes, and no mig20_* database may be left behind.

Tested routes

From Module Result
18.0 website pages, Owl backend widgets, a mail hook 104 blocking, silent or runtime findings → clean scan, clean install, browser smoke test passed
16.0 a renamed fork of an Odoo module detected as a fork, rebased on upstream 20, clean install, 12 of 12 upstream tests passed

Requirements

  • Linux or macOS (Windows: use WSL), with bash, git, rsync and python3.
  • An Odoo 20 community checkout: git clone -b 20.0 --depth 1 https://github.com/odoo/odoo. Enterprise is optional.
  • A Python 3.12+ virtualenv: pip install -r odoo/requirements.txt websocket-client.
  • PostgreSQL 16+, with a role that can create databases.
  • Chrome or Chromium for the browser smoke tests. Without it they are skipped, and the skill says so.
  • A capable agentic model with a large context gives the best results; the workflow is long. The scripts do the heavy, deterministic work (scan, install, test), so results don’t depend on the model remembering Odoo APIs.

What you get

The migrated copy lands in $MIG20_TARGET_ROOT//, inside a git repo with one commit per phase. The run ends with MIGRATION_20.md:

  • the scan summary before and after;
  • what upgrade_code changed and what was done by hand, per area;
  • decisions to confirm, such as access rights, icon mapping and removed dependencies;
  • anything not migrated, with the reason;
  • security findings and data-migration notes for existing databases;
  • what to check by hand.

To try the module, add the project folder to your Odoo 20 addons_path.

Scripts

Script Purpose
install.sh install the skill or its adapters for each agent
scripts/doctor.sh resolve and save the configuration, check prerequisites
scripts/prepare.sh copy the module, detect its version, commit a baseline, fingerprint the source
scripts/fork_check.py detect a renamed fork of an Odoo or vendor module and its Odoo 20 counterpart
scripts/rebase_fork.py rebuild a fork from the upstream Odoo 20 module, renamed, keeping model names
scripts/run_upgrade_code.sh run Odoo’s upgrade scripts safely (per script, module only, core-untouched check)
scripts/scan.py find breaking changes (--json, --area, --fix-tesc, --icons)
scripts/install_test.sh install (and test) on a throwaway database, summarise problems and failure screenshots, drop the database (--tags re-runs one test)
scripts/smoke_test.py generate a browser smoke-test module (clickbot, website pages, flow tours)
scripts/xpath_check.py list every view locator Odoo 20 cannot apply, in one run, without installing the module (odoo-bin shell)
scripts/registry_check.py after the install: overrides of hooks removed from Odoo 20 (real MRO) and fields whose compute/inverse/search method does not exist (odoo-bin shell)
scripts/qweb_if_chain_check.py after the install: QWeb pages where an inherited view separated a t-else/t-elif from its t-if (the page fails only when opened; odoo-bin shell)

Knowledge lives in references/: 15 files on the upgrade tool, manifest, Python/ORM, field renames, controllers, views, QWeb/reports/mail, security, backend Owl 3, website interactions, website builder, website_sale/portal, SCSS/icons, tests and tours, and lessons learned from real migrations.

The scripts also work on their own, without an agent:

S=~/.agents/skills/odoo-migrate-to-20/scripts
python3 $S/scan.py path/to/module                   # every Odoo 20 break, with file:line
python3 $S/fork_check.py path/to/module             # is it a renamed copy of an Odoo module?
$S/install_test.sh path/to/addons_dir my_module     # install on a throwaway database, report errors

Contributing

Every migration should leave the skill better:

  • Add each new kind of break to references/lessons-learned.md, and where possible as a scan.py rule or check.
  • Keep the references verified against the Odoo 20 source, and cite the file that proves each rule.

The README artwork is generated by assets/src/build_assets.py. It uses League Gothic and Barlow Condensed (SIL Open Font License); see the script for where to get the fonts.

License

MIT © tanim913. Odoo is a trademark of Odoo S.A.; this project is independent and not affiliated with or endorsed by Odoo S.A.

View the full README on GitHub

Recommended Tools

Try a different keyword or remove a filter.

Install

npx skillfish add tanim913/odoo-migrate-to-20
New to MCP? Start with the mcpskill.org guide →