Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Odoo Workflow

ASecurity

Mandatory pre-code gate and definition-of-done for ANY Odoo change (add field, override method, inherit view/xpath, wizard, cron, controller, report, security, bug fix, refactor, "optimize", "clean code", "stick to base"), Odoo 16-19. Run BEFORE brainstorming or planning: Odoo design questions are answered by tracing the real source into a Context Brief (file:line), not by open-ended Q&A. Resolves the Odoo version, loads the matching odoo-<major> pack, and closes with the i18n / security / hy...

132 stars
0 votes
0 copies
0 views
Added 9/22/2026
developmentpythongobashnodedockercode-reviewgitapidatabasesecurity

Works with

api

Security Analysis

A100/100

Scanned 9/22/2026

Install to Claude Code

$npx -y skills add unclecatvn/agent-skills --skill odoo-workflow --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Odoo Workflow?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Odoo Workflow
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/unclecatvn-odoo-workflow/badge)](https://www.skillsdirectory.com/skills/unclecatvn-odoo-workflow)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---
name: odoo-workflow
description: >
  Mandatory pre-code gate and definition-of-done for ANY Odoo change (add field, override method,
  inherit view/xpath, wizard, cron, controller, report, security, bug fix, refactor, "optimize",
  "clean code", "stick to base"), Odoo 16-19. Run BEFORE brainstorming or planning: Odoo design
  questions are answered by tracing the real source into a Context Brief (file:line), not by
  open-ended Q&A. Resolves the Odoo version, loads the matching odoo-<major> pack, and closes
  with the i18n / security / hygiene checklist before commit.
globs: "**/*.{py,xml,csv,js,ts,css,scss}"
---

# Odoo Workflow - trace first, then code

Procedure for every Odoo change. The expensive failures are always the same: a field or
method that does not exist in the base addon, an xpath anchor that was never there, an
override hooked on the wrong method, a `sudo()` nobody can audit, a `.pot` that was never
regenerated. The fix is mechanical: **no citation, no code**.

## When this applies

- Any request that ends in Odoo Python or XML: new field, override, inherited view, wizard,
  cron, controller, report, security, bug fix, refactor, "optimize", "clean up".
- Skip for pure questions, documentation-only changes, and git-only tasks (use `odoo-commit`).
- Generic brainstorming/planning skills come *after* Step 2, and only if the business intent
  is still unclear - the Context Brief is the input they need.

## Step 0 - Resolve the Odoo version, load the pack

Resolve `ODOO_VERSION` in this order and stop at the first hit:

1. Explicit statement from the user or the project `CLAUDE.md`.
2. `.odoo-version` at the repo root, `odoo_version` in `.claude/odoo.json`,
   `odoo.version` in `package.json`, `tool.odoo.version` in `pyproject.toml`.
3. `grep -n "^version_info" <odoo>/odoo/release.py` in the core checkout the project runs.
4. `'version'` in workspace `__manifest__.py` files, counting only five-part values such as
   `18.0.1.0.0`. Core addons use short values (`sale` is `'1.2'`) that say nothing.
5. Nothing found: ask once, then offer to write `.odoo-version`. Do not silently assume a version.

Then load the `odoo-<major>` skill (`odoo-16` ... `odoo-19`) and read only the guide the task
needs from its `references/`. If the pack is not installed, say so and continue with the trace
below - the real source is the authority; never fill the gap from memory.

Core files moved in Odoo 19 (`odoo/orm/models.py`, `odoo/orm/fields.py`,
`odoo/modules/module_graph.py`; 16-18 use `odoo/models.py`, `odoo/fields.py`,
`odoo/modules/graph.py`). Cite the path that exists in this project, never one recalled from
another version.

## Step 1 - Trace the chain (grep first, read ranges)

Resolve `ROOTS` once, in `addons_path` order: from `*.conf` / `docker-compose*.yml` /
`.claude/odoo.json`; otherwise
`find . -maxdepth 4 -name __manifest__.py -exec dirname {} \; | xargs -n1 dirname | sort -u`.
Core is two roots, `<odoo>/addons` and `<odoo>/odoo/addons` (`base` lives in the second and
Odoo adds it itself, so configs rarely list it). Enterprise and custom addons are roots too.
When two roots contain a module with the same name, the root listed first is the one that runs.

For every model, method, field, or view the task touches, run the commands below. Replace the
uppercase placeholders; escape dots in `MODEL` (`sale\.order`). Keep the quotes around
`--include` globs - unquoted they fail in zsh.

```bash
# 1. Model definition (also matches `_name = _description = '...'`)
grep -rnE --include='*.py' "_name\s*=\s*([A-Za-z_]+\s*=\s*)?['\"]MODEL['\"]" $ROOTS
# 2. Extensions declared on one line
grep -rnE --include='*.py' "_inherit\s*=.*['\"]MODEL['\"]" $ROOTS
# 3. Extensions whose _inherit list spans several lines (hits print as path-NN-)
grep -rnE --include='*.py' -A12 "_inherit\s*=\s*[[(][^])]*$" $ROOTS | grep -E "['\"]MODEL['\"]"
# 4. Delegation: trace every parent model found here like MODEL
grep -nE "_inherits\s*=" MODEL_FILES
# 5. Field definition, plain or annotated (`commercial_partner_id: Partner = fields.Many2one(`)
grep -nE "^\s+FIELD(\s*:\s*[A-Za-z_.]+)?\s*=\s*fields\." MODEL_FILES
# 6. Every definition of a method
grep -nE "^\s+def METHOD\(" MODEL_FILES
# 7. Hooks a method calls: read its range, then list private calls; trace each hook as METHOD
sed -n 'A,Bp' BASE_FILE | grep -nE "\._[a-z_]+\("
# 8. Views on the model
grep -rlE --include='*.xml' "<field name=\"model\">MODEL</field>" $ROOTS
# 9. Every view and QWeb template inheriting VIEW (an unqualified ref counts only inside MODULE)
grep -rnE --include='*.xml' "name=['\"]inherit_id['\"] ref=['\"](MODULE\.)?VIEW['\"]|inherit_id=['\"](MODULE\.)?VIEW['\"]" $ROOTS
# 10. Anchor nodes: run on the base view file and on every file from 9
grep -nE "name=['\"]ANCHOR['\"]|<notebook|<page |<group |<header|<list|<tree" VIEW_FILES
# 11. ACL and record rules (MODEL with dots as underscores)
grep -rn --include='ir.model.access.csv' "model_MODEL_UNDERSCORED," $ROOTS
grep -rln --include='*.xml' "model_MODEL_UNDERSCORED\"" $ROOTS | grep security
# 12. Before renaming or removing FIELD: every use
grep -rnwE --include='*.py' --include='*.xml' --include='*.js' "FIELD" $ROOTS
# 13. Tests to mirror, and JS only if touched
ls BASE_MODULE/tests/; grep -rnE "def test_.*METHOD" MODULE/tests
grep -rnE --include='*.js' "patch\(|registry\.category\(" MODULE/static
```

`MODEL_FILES` are the files from commands 1-3. For command 12, keep hits on MODEL and list
each one: views, QWeb, `@api.depends`, `depends=`, `related=`, domains, record rules, Python,
tests.

**A file often holds several classes.** Before citing a hit, read up to the nearest `class`
line and confirm its `_name` or `_inherit` is MODEL. `addons/stock/models/stock_location.py`
defines `name` once for `stock.location` and again for `stock.route`.

**A class without `_name`.** Odoo 16-18: a one-item `_inherit` gives its name, otherwise the
class name is used verbatim. Odoo 19: only a string `_inherit` gives the name; anything else
derives it from the class name (`ProductTemplateExtra` becomes `product.template.extra`), so
`_inherit = ['product.template']` without `_name` creates a new model in 19. A class with
`_name = MODEL` that also lists MODEL in `_inherit` is an extension.

Reading budget - this is where tokens go:

- Read ranges, not files: `sed -n 'A,Bp'` or Read with offset/limit, at most 120 lines per hop.
- At most 3 hops inline. Deeper or cross-module flows go to the `odoo-code-tracer` agent with
  `odoo_version` set **if it is installed**; otherwise keep going with the commands above.
- Never re-read a file already cited in the Brief.

## Step 2 - Context Brief (no citation, no code)

Write the brief before any code, one row per symbol the change touches (Odoo 18 example):

| Layer | Symbol | Source (file:line) | Base behaviour | Change |
|-------|--------|--------------------|----------------|--------|
| model | `purchase.order` | `addons/purchase/models/purchase_order.py:19` | `_name`, inherits `mail.thread` | inherit |
| method+super | `button_approve` | `purchase_order.py:528`; override `purchase_stock/models/purchase_order.py:117` calls `_create_picking` | pickings exist only with `purchase_stock` | depends `purchase_stock`, call super, then X |
| path | `stock.picking` `partner_id.commercial_partner_id.vat` | `stock/models/stock_picking.py:630` -> `base/models/res_partner.py:304` -> `:247` | Many2one, Many2one, Char | related field |
| view+xpath | `purchase.purchase_order_form` | `purchase/views/purchase_views.xml:130`, anchor `partner_ref` `:185`, one node in the form | - | add field after anchor |

Layers: model / field / method+super / path / view+xpath / security / data / i18n / test.

The **Source** cell holds exactly one of:

- `file:line` - the cited line itself shows the claim (definition, record, anchor node).
- `NOT FOUND` - every relevant command ran over all ROOTS and nothing on MODEL matched. Name the
  closest real symbol when one exists.
- `UNCERTAIN: <what is missing>` - name the file not read, the runtime-built domain, or the
  other module's view that provides the anchor. Never for a question a command above answers.
- `AMBIGUOUS: <modules>` - overrides in modules that do not depend on each other.

Any cell other than `file:line` means no code for that row: drop it, add the missing `depends`,
or ask. More than 15 rows means the task should be split; never drop a row to fit.

Rules:

- **Redefined field.** Cite the first definition and every redefinition. With the same field
  class, attributes not repeated keep their earlier value (`selection_add` extends the list);
  with a different class, earlier attributes are discarded.
- **Path** (`related=`, `@api.depends`, `depends=`, `domain=`, `domain_force`). One citation per
  hop, comodel read from the definition. A non-relational hop ends the path; a missing hop is
  NOT FOUND at that hop.
- **Depends.** A symbol defined only in a module outside the transitive `depends` is not
  guaranteed to be installed, even if it exists in the current database. Add the dependency or
  drop the row.
- **Override order.** Modules load by dependency depth, then name (19 also by phase). The class
  loaded last runs first and its `super()` reaches the earlier ones. Declare `depends` on every
  module whose override the change relies on. Between modules that do not depend on each other
  the order is an accident of depth and name: write AMBIGUOUS, never guess.
- **Generated at install, never in XML.** `<module>.model_<model>`,
  `<module>.field_<model>__<field>`, `<module>.selection__<model>__<field>__<value>` (dots become
  `_`), and `base.module_category_*` from manifest `category`. Cite the model, field, or
  selection they reflect; `<module>` must define or extend that model. Every other xmlid needs
  its `id="..."` record.
- **Magic fields.** `id` and `display_name` exist on every model. `create_uid`, `create_date`,
  `write_uid`, `write_date` exist when `_log_access` is on, the default for regular and transient
  models, off for abstract ones. Cite the explicit definition when the model declares one.
- **Xpath anchor.** The anchor must be in the base view or in an inheriting view of a module in
  `depends`; otherwise UNCERTAIN. A locator matches the first node, so state how many nodes carry
  that name in the combined views.
- Reuse over invent: before adding a field, helper, or compute, grep for an existing one.
- "Optimize" / "clean code" means: follow the base implementation's pattern with the minimal
  delta. It never means rewriting what `super()` already does.

## Step 3 - Minimal plan

At most 10 bullets: files to touch, files explicitly not touched, what is deferred. One module
unless the Brief proves the change belongs to two. YAGNI: no config for a value that never
changes, no helper used once, no abstraction with one implementation.

## Step 4 - Implement

- Apply the pack's `Coding Conventions` for the guide you opened; existing stable-addon style
  wins over personal preference.
- Override the narrowest hook that runs when the change needs it (command 7), not the public
  method that wraps it. Call `super()` and add the smallest delta; never copy the base body.
- `sudo()` naming: bind any recordset obtained through `sudo()` to a name ending in `_sudo`
  (`partner_sudo = self.partner_id.sudo()`,
  `orders_sudo = self.env['sale.order'].sudo().search(domain)`). A one-shot call
  (`record.sudo().write(vals)`) needs no variable. Never return a `_sudo` recordset from a
  public method or keep it on `self`.
- No `ponytail:` or other "simplified for now" markers in Odoo code - either the code is right
  or the plan in Step 3 says what was deferred.
- New user-facing strings go through `_()` / `_t()` / `string=` / `help=` so they are extractable.

## Step 5 - Definition of done

- [ ] Fresh install whenever the change adds data files, xmlids, views, or security, on a
      throwaway database the project never uses:
      `odoo-bin -c <conf> -d scratch_<module> -i <module> --max-cron-threads=0 --stop-after-init`,
      then `dropdb scratch_<module>`. Installing catches load-order and missing-xmlid errors that
      reading cannot.
- [ ] Tests written for the change and run:
      `odoo-bin -c <conf> -d <db> -u <module> --test-enable --test-tags /<module> --stop-after-init`.
- [ ] `ir.model.access.csv` rows for every new model; record rules where the base model has them.
- [ ] **i18n** - whenever a string changed (`_()`, `_t()`, `string=`, `help=`, selection label,
      report text). Update the module first so the new terms are in the database, then export
      by version:
  - Odoo 16-18: `odoo-bin -c <conf> -d <db> -u <module> --stop-after-init`, then
    `odoo-bin -c <conf> -d <db> --i18n-export=<addons>/<module>/i18n/<module>.pot --modules=<module>`
    (`--modules` is required - the output path does not restrict the export scope).
  - Odoo 19: `odoo-bin i18n export -c <conf> -d <db> <module>` writes `<module>/i18n/<module>.pot`;
    `odoo-bin i18n export -c <conf> -d <db> -l vi_VN <module>` writes the `.po`.
  - Then refresh every shipped locale: `msgmerge --update i18n/<lang>.po i18n/<module>.pot`.
- [ ] **Hygiene** - only added lines are checked, so deleting a forbidden line never trips it:

  ```bash
  git diff HEAD -U0 | grep -E '^\+[^+]' | grep -nE 'ponytail:|Co-Authored-By|Generated with'
  git ls-files --others --exclude-standard | xargs -r grep -nE 'ponytail:|Co-Authored-By|Generated with'
  ```

  Both must print nothing. Before opening a PR also check the commits and the PR body:
  `git log <base>..HEAD --format=%B | grep -nE 'Co-Authored-By|Generated with'`.
- [ ] Commit through `odoo-commit` (`[TAG] module: description`, no AI attribution). The PR body
      explains WHY plus test evidence; no generated-with footer.

## Step 6 - Hand off

Run the `odoo-code-review` agent with `odoo_version` if it is installed. If not, walk its
Security and Hygiene checklists yourself before reporting done.

## Anti-patterns

- Writing a field, method, xmlid, or xpath anchor whose Brief row is not `file:line`.
- Citing a hit without checking which class, and so which model, it belongs to.
- Ordering an override chain from memory or from grep output order instead of `depends`.
- Overriding a public method when the behaviour lives in a hook it calls.
- `UNCERTAIN` without naming what is missing, or for something a Step 1 command answers.
- Reporting `model_*`, `field_*__*`, or magic fields as missing, or inventing any other xmlid.
- Anchoring an xpath on a node that only another module's view adds, without that `depends`.
- Reading whole files in core addons "for context" - use the commands and ranges.
- Binding `.sudo()` results to a variable without the `_sudo` suffix, or leaking it out of the method.
- Shipping strings without regenerating the `.pot` and merging the `.po` files.

Attribution

unclecatvnunclecatvn
View sourceMore from unclecatvn →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

284722 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2192 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →