> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/administration-guide/lifecycle/disable-leavers-automatically.md).

# Disable leavers automatically on their end date

LEAVER\_PROCESS ships enabled at 17:00 daily and, with an empty Parameter 1, selects nobody. Give it a window and a script that skips service accounts.

## What this is, and when you need it

A birthright rule takes access away when someone stops matching, which covers transfers. It does not cover the leaver, who usually stops matching only when somebody edits their record. The mechanism that does ships **enabled** and is easy to overlook: batch task `LEAVER_PROCESS`, *Disable by last date*, cron `0 0 17 * * ?`. It is enabled and it is almost certainly doing nothing on your installation, because the window it searches comes from **Parameter 1** on the task row, and an empty **Parameter 1** selects nobody.

## At a glance

|                          |                                                                                                                                         |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | `LEAVER_PROCESS` running your own leaver script nightly, with a window that survives a missed run and guards that skip service accounts |
| **Where**                | **Administration → Batch Tasks → `LEAVER_PROCESS`**; **Administration → Groovy Manager**; property `org.openiam.idm.leaver.script`      |
| **You need first**       | One disposable user with a past end date, one service account, and the employee-type IDs you must never disable                         |
| **Takes**                | 30 minutes, plus one **Execute** run per test user                                                                                      |
| **Reversible**           | Partly — re-point the property. But a user this task disabled stays disabled, and the target account has already changed                |

## Before you begin

* **The stored employee-type IDs that must never be disabled.** IDs, not labels: the shipped entry displayed as **SYSTEM ACCOUNT** stores `SYS_ACCOUNT`.
* **A disposable user** whose **End date** you can set to yesterday, and a service account to prove the guard.
* **The task's current Parameter 1**, recorded first.

{% hint style="warning" %}
This task disables people, at 17:00 every day. Test with **Execute** against one user first — see [Change configuration safely](/administration-guide/start/change-safely.md).
{% endhint %}

## Give the task a window and a guarded script

Clone the shipped script rather than writing one. This version keeps its structure and adds the two guards every deployment needs: never disable a service account, never re-disable someone already gone.

{% stepper %}
{% step %}

### Copy the shipped script to a path you own

Open `batch/LeaverProcess_1.0.groovy` in **Administration → Groovy Manager** and save its source under your own path, for example `customer/batch/LeaverProcessTask`, version `1.0`. Keep the class declaration and both method signatures exactly as the shipped script has them; the task instantiates your class as an `AbstractLeaverProcess` and a class that no longer matches fails at run time rather than at save time.
{% endstep %}

{% step %}

### Add the two guards

Adapt the script below. `PROTECTED_EMPLOYEE_TYPE_IDS` holds stored IDs, not labels: the shipped entry displayed as **SYSTEM ACCOUNT** stores `SYS_ACCOUNT`. Read your own values off a representative user rather than transcribing what the drop-down shows.

It calls `userRabbitMQService` and `provisionService` without declaring either. That is deliberate and it is safe **on this task**: the leaver task wires every inherited service field before it calls your script. The activation task does not, so do not carry this style across — [which inherited fields each task populates](/administration-guide/reference/shipped-batch-tasks.md#which-inherited-service-fields-your-script-can-use) is the one place that is stated.

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```groovy
import org.openiam.base.ws.DateSearchParam
import org.openiam.base.ws.MatchType
import org.openiam.batch.manager.initializer.implementation.AbstractLeaverProcess
import org.openiam.dto.User
import org.openiam.idm.searchbeans.UserSearchBean
import org.openiam.idm.srvc.user.dto.UserStatusEnum
import org.openiam.provision.dto.user.request.DisableUserProvisioningRequest

/**
 * Leaver process: disable every user whose End date fell inside the window,
 * except protected identities and users who are already gone.
 *
 * daysBeforeLeaver comes from Parameter 1 on the LEAVER_PROCESS batch task.
 * It is floored at 1 here, because a value of 0 searches an empty range.
 */
class LeaverProcessTask extends AbstractLeaverProcess {

    /**
     * Employee type IDs this task must never touch. These are stored IDs, not
     * labels: the shipped entry displayed as SYSTEM ACCOUNT stores 'SYS_ACCOUNT'.
     */
    static final Set<String> PROTECTED_EMPLOYEE_TYPE_IDS = ['SYS_ACCOUNT'] as Set

    private static Date midnight(int daysAgo) {
        Calendar c = Calendar.getInstance()
        c.add(Calendar.DAY_OF_MONTH, -daysAgo)
        c.set(Calendar.HOUR_OF_DAY, 0)
        c.set(Calendar.MINUTE, 0)
        c.set(Calendar.SECOND, 0)
        c.set(Calendar.MILLISECOND, 0)
        return c.getTime()
    }

    @Override
    List<User> getUsers(int daysBeforeLeaver) {
        int days = Math.max(daysBeforeLeaver, 1)
        Date from = midnight(days)
        Date to = midnight(0)
        println "Leaver process: End date from ${from} to ${to}"

        UserSearchBean usb = new UserSearchBean()
        usb.setLastDateToken(new DateSearchParam(from, to, MatchType.BETWEEN))
        List<User> found = userRabbitMQService.findBeans(usb, null, 0, Integer.MAX_VALUE)?.getList()
        if (found == null || found.isEmpty()) {
            println "Leaver process: no users in window"
            return []
        }

        List<User> selected = found.findAll { User u ->
            if (PROTECTED_EMPLOYEE_TYPE_IDS.contains(u.employeeTypeId)) {
                println "Leaver process: skipping protected identity ${u.id}"
                return false
            }
            if (UserStatusEnum.TERMINATED == u.status || UserStatusEnum.DEACTIVATED == u.status) {
                return false
            }
            return true
        }
        println "Leaver process: ${selected.size()} of ${found.size()} users selected"
        return selected
    }

    @Override
    void perform(User user) {
        println(String.format("Disable User %s with Id %s", user.getDisplayName(), user.getId()))
        DisableUserProvisioningRequest request = new DisableUserProvisioningRequest()
        request.setId(user.getId())
        provisionService.disableUser(request)
    }
}
```

{% endcode %}
{% endstep %}

{% step %}

### Point the property at your script

Set `org.openiam.idm.leaver.script` to your new script's ID under **Administration → System Configuration**. Until you do, the task still runs the shipped script.
{% endstep %}

{% step %}

### Set Parameter 1 on the task

**Administration → Batch Tasks → `LEAVER_PROCESS`**, **Parameter 1**. Set it to at least the number of days the task could plausibly be down. With `1`, a user whose end date is today is not picked up until tomorrow evening, and a run missed on Friday loses Thursday's leavers for good.
{% endstep %}

{% step %}

### Test on one disposable user, with **Execute**

Set one disposable user's **End date** to yesterday, then press **Execute** on the task — that runs it once regardless of the Enabled flag. Check the audit for `AUTOMATIC_USER_TERMINATION` against that user and nobody else, then check the account in the managed system. Every `println` in the script goes to the batch-task-manager service log, which is where the skip lines and the selected count appear.
{% endstep %}

{% step %}

### Confirm the protected identities were skipped

Set a service account's **End date** to yesterday as well and re-run. The log should carry `Leaver process: skipping protected identity <id>` and the account must still be active. This is the check that matters most, because the failure mode is silent and wide.
{% endstep %}
{% endstepper %}

## How the task works

`LEAVER_PROCESS` is a thin wrapper around a Groovy script you own. It reads the script ID from `org.openiam.idm.leaver.script`, instantiates it, asks it for a list of users, and calls it once per user — recording `TERMINATE_USERS_BY_LAST_DATE` for the run and `AUTOMATIC_USER_TERMINATION` per user, with the failure reason attached to the per-user record if the script throws. The shipped script, `batch/LeaverProcess_1.0.groovy`, selects users whose **End date** falls inside a window and disables each one.

{% hint style="warning" %}
**The window is set by Parameter 1 on the task, and an empty Parameter 1 selects nobody.** The shipped script searches for End dates between *n* days ago and midnight this morning, where *n* is the task's first parameter. With no value the task runs every evening at 17:00, searches an empty range, finds nothing, and reports success. Check it under **Administration → Batch Tasks → `LEAVER_PROCESS`**, and set it to at least the number of days the task could plausibly be down — with `1`, a user whose end date is today is not picked up until tomorrow evening, and a run missed on Friday loses Thursday's leavers for good.
{% endhint %}

## What this task does not do

Disabling a user is not the same as unwinding their entitlements, and nothing else picks that up for you.

* **It does not remove or end-date memberships.** The user keeps every role, group, resource and organization affiliation they held. Decide separately whether the leaver's memberships must go, and by what — see [Advanced lifecycle patterns](/administration-guide/lifecycle/advanced-lifecycle-patterns.md).
* **End dates on memberships are not acted on either.** `AUTO_REVOCATION` — *Notification of expiring/revoke of expired access*, cron `0 0 20 * * ?` — ships **disabled**, so an end date that passes revokes nothing until you enable it.
* **It does not delete anything.** `DELETE_PENDING` — *Delete/Deactivate by last Data*, cron `0 0/30 * * * ?` — also ships disabled.
* **It disables the OpenIAM identity.** Whether the account in each managed system is disabled or deleted is decided by that system's `ON_DELETE` resource property, and by whether provisioning succeeded.

## Verification

| What to check                           | Where                                                                                                              |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| The run happened                        | Audit `TERMINATE_USERS_BY_LAST_DATE`, and `LAST_EXEC_TIME` on the task advanced                                    |
| Which users it acted on                 | Audit `AUTOMATIC_USER_TERMINATION`, one record per user, with the failure reason attached if the script threw      |
| Your script ran and not the shipped one | `org.openiam.idm.leaver.script` holds your script's ID, and the log carries the `println` lines your script writes |
| The window is what you meant            | The `Leaver process: End date from … to …` line in the batch-task-manager log                                      |
| Protected identities were skipped       | `Leaver process: skipping protected identity <id>` in the same log, and the account still active                   |
| The far end changed                     | The account in the managed system, not the OpenIAM status alone                                                    |

## When it goes wrong

| Symptom                                                               | Cause to check                                                                                             | Fix                                                                                                                       |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| The task runs every evening and never disables anyone                 | **Parameter 1** is empty, so the script searches an empty date range                                       | Set it to at least the number of days the task could plausibly be down                                                    |
| A user whose end date was today was not picked up                     | With `1`, the window ends at midnight this morning                                                         | Expected. Widen the window, or accept the one-day delay                                                                   |
| A run missed on Friday lost Thursday's leavers                        | The window is relative to the run, not a high-water mark                                                   | Widen **Parameter 1** past the longest plausible outage                                                                   |
| A service account was disabled                                        | The guard list holds a label rather than the stored employee-type ID                                       | Read the stored ID off the account itself; the shipped **SYSTEM ACCOUNT** entry stores `SYS_ACCOUNT`                      |
| The shipped behaviour persists after you saved a new script           | `org.openiam.idm.leaver.script` still holds the old script's ID                                            | Re-point the property, then wait out `org.openiam.groovy.cache.ttl.ms` (60 s)                                             |
| The script throws and the run still reports success                   | The per-user failure reason is attached to that user's `AUTOMATIC_USER_TERMINATION` record, not to the run | Read the per-user records, not the run                                                                                    |
| Users are disabled in OpenIAM but their target accounts are untouched | An OpenIAM status change is not a target change                                                            | Troubleshoot provisioning for that managed system                                                                         |
| Nothing runs at all, on this or any other task                        | The scheduler's cycle is aborting                                                                          | [Restart batch tasks that have all stopped](/administration-guide/automation/batch-tasks/diagnose-stalled-batch-tasks.md) |

## Roll it back

Point `org.openiam.idm.leaver.script` back at `batch/LeaverProcess.groovy`, or untick **Is Active** on the task and delete its pending schedule rows — disabling alone does not stop an already-materialised row from running. Neither re-enables a user the task has already disabled: that is a deliberate act per user, and the target account has to be re-provisioned.

## Related

* [Grant birthright access](/administration-guide/lifecycle/birthright-access.md) — the other half of the lifecycle, and the rule that covers transfers
* [Create a batch task](/administration-guide/automation/batch-tasks.md) — the task row, **Execute**, and the two timers
* [Shipped batch tasks](/administration-guide/reference/shipped-batch-tasks.md) — `LEAVER_PROCESS`, `AUTO_REVOCATION` and the rest
* [Create a Groovy script](/administration-guide/automation/manage-groovy-scripts.md) — saving and versioning the leaver script
* [Advanced lifecycle patterns](/administration-guide/lifecycle/advanced-lifecycle-patterns.md) — unwinding the leaver's entitlements

*Applies to OpenIAM 2026.8.1. Last reviewed 7 August 2026.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/administration-guide/lifecycle/disable-leavers-automatically.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
