Product
Automation Rules
Automation Rules

Automation Rules

Automation rules let you define when and what oec.sh should do automatically — deploy staging when code is pushed to main, refresh UAT from production on demand, promote a tested build to production after human approval. Each rule pairs a trigger with an action, plus optional conditions to keep things running safely.


How It Works

A rule watches for a git event or waits to be called manually. When the trigger fires, the rule evaluates its conditions (branch patterns, cooldowns, file filters). If conditions pass — and an approval gate is not blocking — the action executes in the background and the result is logged.

Git push → trigger matches → conditions evaluated → [approval?] → action runs → run logged

You can see all rules on the Automation tab inside a project. Each rule shows its trigger type, last run status, and the notification channels subscribed to its events.


Triggers

A trigger defines what event starts the rule.

Push (push)

Fires when code is pushed to a branch. Use branch patterns to scope it.

"trigger_config": {
  "branch_patterns": ["main", "staging", "release/*"]
}

Glob patterns are supported: main matches exactly, feature/* matches any branch starting with feature/, * matches everything.

Pull Request Opened (pr_open)

Fires when a pull request is opened. Useful for spinning up a testing deploy when a developer raises a PR.

"trigger_config": {
  "target_branch_patterns": ["main"],
  "source_branch_patterns": ["feature/*", "fix/*"]
}

Both target_branch_patterns (the branch the PR targets) and source_branch_patterns (the PR's own branch) are optional — omit either to match all.

Pull Request Merged (pr_merge)

Fires when a pull request is merged. Avoids mid-review deploys — code only ships after the PR is approved and merged.

"trigger_config": {
  "target_branch_patterns": ["main"]
}

Tag Push (tag_push)

Fires when a git tag is pushed. Commonly used for release pipelines.

"trigger_config": {
  "tag_pattern": "v*"
}

The tag_pattern is a glob: v* matches v1.0.0, v2.3.1, etc. Omit it to fire on any tag.

Manual (manual)

No git event is required. The rule runs when explicitly triggered from the Automation tab or via API. Use this for operations that require a deliberate human decision — staging refreshes, UAT resets, on-demand deploys.

Manual rules can still have an approval gate (see Approval Gates).


Actions

The action is what oec.sh does when a rule fires.

Deploy (deploy)

Performs a full deployment of the target environment from its configured git branch. Rebuilds containers, runs migrations, restarts Odoo.

"action_config": {
  "action_type": "deploy",
  "target_env_id": "ENV_UUID",
  "force": false
}

Set force: true to redeploy even if the environment is already on the latest commit.

Quick Update (quick_update)

Runs a lightweight git pull + optional module operation + container restart, without rebuilding Docker images. Much faster than a full deploy (~30 seconds vs minutes). Use the mode field to control what happens after the git pull.

"action_config": {
  "action_type": "quick_update",
  "target_env_id": "ENV_UUID",
  "mode": "pull_restart"
}

Modes

ModeWhat it doesWhen to use
pull_restartGit pull → restart containerCode-only changes (default)
update_allGit pull → odoo -u all --stop-after-init → restartAll modules need upgrading
update_specificGit pull → odoo -u <modules> --stop-after-init → restartUpgrade specific modules (schema changes, data updates)
reinitialize_specificGit pull → odoo -i <modules> --stop-after-init → restartInstall or reinitialize specific modules
restart_onlyRestart container only (no git pull)Quickest restart, no code change

For update_specific and reinitialize_specific, provide the module names in modules:

"action_config": {
  "action_type": "quick_update",
  "target_env_id": "ENV_UUID",
  "mode": "update_specific",
  "modules": ["sale", "purchase", "account_move_line_tax_details"]
}

Difference between -u and -i:

  • update_specific (-u) — Upgrade existing modules: re-runs Python init methods, XML data files, migration scripts. Use when a module has schema or data changes.
  • reinitialize_specific (-i) — Install or reinitialize modules: installs modules that aren't yet installed, or forces a clean re-initialization. Use when adding a new module to an existing database.
⚠️

Quick Update skips Docker image rebuilds and Python dependency installation. Use a full Deploy when you've added new Python packages or system-level migrations.

Promote (promote)

Deploys the exact git SHA and module state from a source environment to a target environment — no rebuild from scratch. The target ends up running precisely what QA tested on the source.

"action_config": {
  "action_type": "promote",
  "source_env_id": "STAGING_ENV_UUID",
  "target_env_id": "PROD_ENV_UUID"
}

This is the safest way to move a tested build to production — there are no "build surprises" since the exact same artifact is promoted.

Clone, Neutralize & Deploy (clone_neutralize_deploy)

Clones a source environment's database (and optionally filestore) into a target environment, runs Odoo's native neutralize command to make it safe for non-production use, then deploys. Perfect for keeping staging in sync with production data.

"action_config": {
  "action_type": "clone_neutralize_deploy",
  "source_env_id": "PROD_ENV_UUID",
  "target_env_id": "STAGING_ENV_UUID",
  "neutralize": true,
  "include_filestore": false,
  "git_branch_override": null
}

Neutralize disables outgoing emails, payment providers, crons, OAuth, webhooks, and external integrations so the cloned environment cannot accidentally contact real systems.

Set include_filestore: false to skip copying uploaded files — this makes the refresh ~80% faster and is sufficient for most development and QA scenarios.

Use git_branch_override to deploy a different branch after the clone (e.g. deploy develop branch onto the freshly-cloned data).

Neutralize uses Odoo's built-in odoo-bin neutralize command. It handles disabling correctly for all versions (Odoo 16, 17, 18, 19).

Destroy Environment (destroy_env)

Permanently destroys an environment — stops containers, removes database, releases resources. Use with care.

"action_config": {
  "action_type": "destroy_env",
  "target_env_id": "ENV_UUID"
}
⚠️

This action is irreversible. Always pair it with an approval gate for anything other than ephemeral test environments.


Conditions

Conditions are checked after the trigger fires but before the action runs. If any condition fails, the run is recorded as skipped with a reason.

Branch Patterns

Set in trigger_config.branch_patterns — the rule only fires if the pushed branch matches one of the patterns.

Cooldown

Prevents the rule from firing more than once within a time window.

"conditions": {
  "cooldown_minutes": 5
}

If the rule last ran less than cooldown_minutes ago, the run is skipped. Set to 0 (default) to allow unlimited frequency.

File Path Filters

Only fire if the commit changed files matching certain patterns — or skip if only excluded paths changed.

"conditions": {
  "include_paths": ["requirements.txt", "manifest.json", "*.py"],
  "exclude_paths": ["docs/*", "README.md", "*.mdx"]
}
  • include_paths: At least one changed file must match one of these patterns. If none match, the run is skipped.
  • exclude_paths: If all changed files match these patterns, the run is skipped. Useful for ignoring documentation-only commits.

Environment Status Requirement

Only run the action if the target environment is in a particular state.

"conditions": {
  "require_env_status": "running"
}

Options: "running", "stopped", or null (any status — the default).


Approval Gates

Add a human sign-off step before the action executes. Useful for production deployments where an unreviewed deploy could cause downtime.

"require_approval": true,
"approval_expires_minutes": 60

Approval workflow:

  1. Rule fires → run is created with status pending_approval
  2. A team member reviews and approves or rejects from the run detail view
  3. Approved → action executes; Rejected → run stops
  4. If no action is taken within approval_expires_minutes, the run expires automatically

Approval windows range from 5 to 1440 minutes (24 hours).


Circuit Breaker

The circuit breaker automatically disables a rule after a run of consecutive failures, preventing a broken rule from hammering your infrastructure.

"circuit_breaker": {
  "enabled": true,
  "max_consecutive_failures": 3,
  "window_minutes": 60
}

When max_consecutive_failures is reached, the rule is paused (is_active set to false) and must be manually re-enabled from the Automation tab. Successful runs reset the failure counter.

Recommended settings:

  • Development/staging rules: max_consecutive_failures: 3 — allows a few transient failures
  • Production rules: max_consecutive_failures: 1 — fail fast, get a human involved immediately

Retry Policy

Control how many times individual deployment steps are retried before a run is marked as failed.

"retry_policy": {
  "build_max_attempts": 2,
  "container_start_max_attempts": 2,
  "health_check_max_attempts": 5
}
SettingDefaultRangeWhat it retries
build_max_attempts20–5Docker image build failures
container_start_max_attempts20–5Container start failures
health_check_max_attempts51–20Odoo health check failures
⚠️

Database migrations are never retried automatically (migration_retry is always false). This prevents double-migration data corruption.


Rule Priority

When multiple rules can fire on the same event, they execute in priority order. Lower numbers run first.

"priority": 10

Valid range: 1–9999 (default: 100). Reorder rules from the Automation tab by dragging, or bulk-update priorities via the API.


Templates

The Automation tab includes a template gallery with 10 pre-built rule configurations for common workflows. Templates auto-resolve environment slots by matching environment names — select a template, confirm the environment mappings, and the rule is ready.

TemplateTriggerActionBest for
Deploy staging on push to mainPush → mainDeploy stagingContinuous integration
Quick-update dev on feature pushPush → feature/*Quick Update devFast inner loop
Deploy staging on PR mergePR merge → mainDeploy stagingReview-gated deploys
Production release on version tagTag push v*Deploy prodVersioned releases
Promote staging to productionTag push release/*PromoteTested build promotion
Refresh staging from productionManualClone + neutralizeFresh data for QA
Refresh UAT (no filestore)ManualClone (fast)~80% faster refresh
Deploy PR to staging for reviewPR openDeploy stagingPR preview deploys
Quick-update staging on PR re-pushPush → PR branchQuick UpdateFast re-test after fixes
Manual production deploy (approval)ManualDeploy prodSafety-gated prod deploys

Use Cases

Real-world rule configurations for the most common Odoo agency workflows. Each example shows the full rule JSON you'd send via API — or the equivalent settings in the UI rule editor.


1. Auto-deploy staging when code lands on main

The bread-and-butter CI rule. Every merge to main triggers a full staging deploy. A 5-minute cooldown prevents rapid-fire merges from queuing up multiple deploys.

{
  "name": "Deploy staging on push to main",
  "trigger_type": "push",
  "trigger_config": { "branch_patterns": ["main"] },
  "action_type": "deploy",
  "action_config": {
    "action_type": "deploy",
    "target_env_id": "STAGING_ENV_UUID"
  },
  "conditions": { "cooldown_minutes": 5 },
  "circuit_breaker": { "enabled": true, "max_consecutive_failures": 3 }
}

2. Quick-update dev on every feature push (~30 seconds)

Fast inner-loop for developers. Any push to a feature/* branch triggers a git pull + restart on the dev environment — no Docker rebuild, no downtime, ready in ~30 seconds.

{
  "name": "Quick update dev on feature push",
  "trigger_type": "push",
  "trigger_config": { "branch_patterns": ["feature/*", "fix/*", "chore/*"] },
  "action_type": "quick_update",
  "action_config": {
    "action_type": "quick_update",
    "target_env_id": "DEV_ENV_UUID",
    "mode": "pull_restart"
  },
  "conditions": { "cooldown_minutes": 1 }
}

3. Upgrade specific modules after a push

When you push module changes that include schema updates (new fields, model changes), automatically run odoo -u <modules> on the target environment. Faster than a full deploy for targeted module work.

{
  "name": "Upgrade sales modules on push to module-dev",
  "trigger_type": "push",
  "trigger_config": { "branch_patterns": ["module-dev", "feature/sales-*"] },
  "action_type": "quick_update",
  "action_config": {
    "action_type": "quick_update",
    "target_env_id": "DEV_ENV_UUID",
    "mode": "update_specific",
    "modules": ["sale", "sale_management", "crm"]
  },
  "conditions": { "cooldown_minutes": 2 }
}

Use update_specific (-u) for modules that already exist in the database and have code/schema changes. It re-runs init methods, loads updated XML data files, and runs migration scripts.


4. Install a new module after a push

When you add a new custom module to your addons path and push, automatically install it on the dev environment with odoo -i <module>. Use this instead of -u for modules not yet present in the database.

{
  "name": "Install new module on push",
  "trigger_type": "push",
  "trigger_config": { "branch_patterns": ["feature/new-module-*"] },
  "action_type": "quick_update",
  "action_config": {
    "action_type": "quick_update",
    "target_env_id": "DEV_ENV_UUID",
    "mode": "reinitialize_specific",
    "modules": ["my_new_custom_module"]
  },
  "conditions": { "cooldown_minutes": 2 }
}

Use reinitialize_specific (-i) when the module does not yet exist in the database. For modules that are installed but need updating, use update_specific (-u) instead.


5. Upgrade all modules after a release branch push

For release branches where you want a complete module refresh, run odoo -u all on staging after each push. Slower than specific mode but guarantees all modules are consistent.

{
  "name": "Full module update on release push",
  "trigger_type": "push",
  "trigger_config": { "branch_patterns": ["release/*"] },
  "action_type": "quick_update",
  "action_config": {
    "action_type": "quick_update",
    "target_env_id": "STAGING_ENV_UUID",
    "mode": "update_all"
  },
  "conditions": { "cooldown_minutes": 10 }
}

6. Deploy staging only when Python or manifest files change

Skip deploys for documentation or config-only commits. The include_paths filter ensures a full deploy only runs when code that actually affects the running application changes.

{
  "name": "Deploy staging on meaningful changes",
  "trigger_type": "push",
  "trigger_config": { "branch_patterns": ["main"] },
  "action_type": "deploy",
  "action_config": {
    "action_type": "deploy",
    "target_env_id": "STAGING_ENV_UUID"
  },
  "conditions": {
    "cooldown_minutes": 5,
    "include_paths": ["*.py", "__manifest__.py", "requirements.txt", "*.xml", "*.csv"],
    "exclude_paths": ["docs/*", "*.md", "*.mdx", ".gitignore"]
  }
}

7. Refresh staging from production (manual, on demand)

Keep staging in sync with real customer data. Triggered manually — a developer clicks "Run" in the Automation tab when they need fresh data for a QA cycle. Neutralize ensures no emails or payments fire from the cloned data.

{
  "name": "Refresh staging from production",
  "trigger_type": "manual",
  "trigger_config": {},
  "action_type": "clone_neutralize_deploy",
  "action_config": {
    "action_type": "clone_neutralize_deploy",
    "source_env_id": "PRODUCTION_ENV_UUID",
    "target_env_id": "STAGING_ENV_UUID",
    "neutralize": true,
    "include_filestore": false,
    "git_branch_override": null
  },
  "conditions": { "cooldown_minutes": 60 }
}

Set include_filestore: false to skip uploaded attachments — the refresh is ~80% faster and sufficient for most QA scenarios. Set it to true only if you need to test file-related features.


8. Refresh UAT from production including filestore

When QA needs an exact copy of production including uploaded files (invoices, attachments), set include_filestore: true. Requires more time and disk space.

{
  "name": "Full UAT refresh (with filestore)",
  "trigger_type": "manual",
  "trigger_config": {},
  "action_type": "clone_neutralize_deploy",
  "action_config": {
    "action_type": "clone_neutralize_deploy",
    "source_env_id": "PRODUCTION_ENV_UUID",
    "target_env_id": "UAT_ENV_UUID",
    "neutralize": true,
    "include_filestore": true
  },
  "conditions": { "cooldown_minutes": 120 }
}

9. Promote staging to production after a release tag

Push a v* tag on main → staging gets validated → production is promoted with the exact same build staging was tested on. No rebuild, no surprises.

{
  "name": "Promote to production on release tag",
  "trigger_type": "tag_push",
  "trigger_config": { "tag_pattern": "v*" },
  "action_type": "promote",
  "action_config": {
    "action_type": "promote",
    "source_env_id": "STAGING_ENV_UUID",
    "target_env_id": "PRODUCTION_ENV_UUID"
  },
  "require_approval": true,
  "approval_expires_minutes": 120,
  "circuit_breaker": { "enabled": true, "max_consecutive_failures": 1 }
}
⚠️

The max_consecutive_failures: 1 circuit breaker on the production rule ensures one failure immediately pauses the rule and alerts the team — fail fast rather than retrying.


10. Deploy to production with approval gate

Full production deploy triggered manually. Requires a team member to review and approve before the deploy executes. The 2-hour approval window gives enough time for async review.

{
  "name": "Production deploy (requires approval)",
  "trigger_type": "manual",
  "trigger_config": {},
  "action_type": "deploy",
  "action_config": {
    "action_type": "deploy",
    "target_env_id": "PRODUCTION_ENV_UUID"
  },
  "require_approval": true,
  "approval_expires_minutes": 120,
  "circuit_breaker": { "enabled": true, "max_consecutive_failures": 1 }
}

11. Deploy PR branch to staging when PR is opened

Automatically deploy the PR's branch to a staging environment when any PR targeting main is opened. QA can review the feature immediately without waiting for manual deploys.

{
  "name": "Deploy PR to staging on PR open",
  "trigger_type": "pr_open",
  "trigger_config": {
    "target_branch_patterns": ["main"],
    "source_branch_patterns": ["feature/*", "fix/*"]
  },
  "action_type": "deploy",
  "action_config": {
    "action_type": "deploy",
    "target_env_id": "STAGING_ENV_UUID"
  },
  "conditions": {
    "cooldown_minutes": 5,
    "require_env_status": "running"
  }
}

12. Quick-update staging every time a PR is re-pushed

After QA comments on a PR and the developer pushes a fix, staging should reflect the latest commit immediately. This rule targets the PR's source branch — any push refreshes staging without a full rebuild.

{
  "name": "Quick update staging on PR re-push",
  "trigger_type": "push",
  "trigger_config": { "branch_patterns": ["feature/*", "fix/*"] },
  "action_type": "quick_update",
  "action_config": {
    "action_type": "quick_update",
    "target_env_id": "STAGING_ENV_UUID",
    "mode": "pull_restart"
  },
  "conditions": {
    "cooldown_minutes": 2,
    "require_env_status": "running"
  }
}

13. Full deploy on PR merge to main

After a PR is merged (not just pushed), perform a full deploy — this is safer than pushing because you know the code has been code-reviewed and the branch is clean.

{
  "name": "Full deploy staging on PR merge",
  "trigger_type": "pr_merge",
  "trigger_config": { "target_branch_patterns": ["main"] },
  "action_type": "deploy",
  "action_config": {
    "action_type": "deploy",
    "target_env_id": "STAGING_ENV_UUID"
  },
  "conditions": { "cooldown_minutes": 5 },
  "circuit_breaker": { "enabled": true, "max_consecutive_failures": 3 }
}

Choosing the Right Quick Update Mode

ScenarioRecommended mode
Pushed a Python logic fix, no schema changepull_restart
Added a new field to an existing modelupdate_specific with that module
Changed XML views or data filesupdate_specific with that module
Added a brand-new module to addonsreinitialize_specific with the new module
Multiple modules changed, unsure whichupdate_all
Changed only static assets or JSpull_restart
Need containers to pick up env var changesrestart_only
Added a Python package (requirements.txt)Use Deploy (full rebuild needed)

Run History

Every time a rule fires, a run is created — whether it executed, was skipped, or is waiting for approval.

Go to Automation tab → select a rule → Run History to see per-rule runs, or view all project runs across all rules from the project-level automation log.

Run Statuses

StatusMeaning
pendingQueued, waiting to be processed
runningAction is executing
completedAction finished successfully
failedAction encountered an unrecoverable error
skippedConditions were not met (shows skip reason)
pending_approvalWaiting for a team member to approve
approvedApproved, action is executing
rejectedA team member rejected the run
approval_expiredApproval window closed with no response

What's in a Run

Each run record includes:

  • The trigger that fired (event type, branch, commit SHA, actor)
  • Start and end time
  • Per-step timing breakdown
  • Error message for failed runs
  • Link to the deployment created by the run (for deploy/promote/quick-update actions)

Notifications

When a rule completes or fails, oec.sh fires automation_rule.completed or automation_rule.failed events. Subscribe to these with an outgoing webhook or email notification channel to alert your team.

The Automation tab shows small indicator pills on each rule row — green for webhooks, blue for email channels — so you can see at a glance which rules have notifications configured.

To set up notifications: go to Settings → Webhooks or Settings → Email Alerts and subscribe to automation_rule.completed and/or automation_rule.failed. You can scope a channel to the specific project if you only want events from one project.

See Notifications for setup instructions.


Permissions

Creating, editing, and deleting automation rules requires the Developer role or higher on the project.

Approving or rejecting pending runs requires the Admin role or higher.

Viewing rules and run history is available to all project members.


API

All automation rule operations are available via the REST API for CI/CD integration and programmatic management.

curl "https://api.oec.sh/api/v1/projects/PROJECT_ID/automation-rules" \
  -H "Authorization: Bearer YOUR_TOKEN"