# l4 commitments expiring

> List the commitments whose term ends inside a window, and fail a pipeline when one is too close to lapsing.

Source: https://docs.levelfour.ai/cli/commands/commitments/expiring


`l4 commitments expiring` lists the commitments whose term ends inside a window. With
`--fail-within` it also returns exit code `2`, which is what makes it usable as a build gate.

```bash
l4 commitments expiring [flags]
```

A term ending is not an event anything currently interrupts. The commitment simply stops, and the
usage it was covering reverts to on-demand rates on the next bill. This command is how that becomes
something a pipeline notices.

## Flags

| Flag                | Description                                                            |
| ------------------- | ---------------------------------------------------------------------- |
| `--within <w>`      | Window to list. Defaults to `90d`                                      |
| `--fail-within <w>` | Exit `2` when a commitment lapses inside this window. Unset by default |
| `--provider <id>`   | `aws` (expiry is not measured elsewhere)                               |

Durations take `d`, `w` or `m`. A month is 30 days.

Global flags apply. See [CLI overview](/cli#global-flags).

## The gate

Without `--fail-within` the command only reports, and always exits `0`.

```bash
l4 commitments expiring --fail-within 30d
```

```text
ID       SERVICE  KIND  HOLDER  EXPIRES  UTIL   COVERAGE      PROTECTS/MO
ri-a1b2  EC2      RI    prod    18d      98.2%  61.4%         $12400.00

Error: 1 commitment(s) lapse within 30d.
```

```bash
$ echo $?
2
```

Exit `2` is this CLI's "issues found" code, shared with
[`l4 estimate --fail-above`](/cli/commands/estimate) and
[`l4 diff --fail-above`](/cli/commands/diff#ci-gate). It means the command ran correctly and did not
like what it found, which is distinct from `1`, a command that failed.

### The listing widens to match

Passing only `--fail-within` widens the listing to the same window, so the output explains the exit
code it just produced. Pass `--within` as well to keep a wider view than the gate:

```bash
l4 commitments expiring --within 180d --fail-within 30d
```

That lists six months of upcoming expiries and fails on the next thirty days.

### It survives the output flags

The gate runs whatever the output format is. Under `--json`, `--jq` or `--template` the payload goes
to stdout and both the pass and fail notices go to stderr, so the document stays parseable and the
exit code still arrives:

```bash
l4 commitments expiring --fail-within 30d --json > expiring.json
echo $?
```

> **Warning**
>
> This is worth checking if you fork this behaviour anywhere. A gate that stops firing the moment someone adds `--json` is the failure the gate exists to prevent, and it is invisible in any test that only exercises the happy path.

Under `--quiet` nothing prints at all and the exit code is the whole result, which is the documented
contract for that flag.

## In a pipeline

GitHub Actions:

```yaml
- name: Commitment expiry check
  env:
    LEVELFOUR_TOKEN: ${{ secrets.LEVELFOUR_TOKEN }}
  run: l4 commitments expiring --fail-within 30d
```

GitLab CI:

```yaml
commitment-expiry:
  script:
    - l4 commitments expiring --fail-within 30d
```

A read-only key is enough. See [CLI authentication](/cli/authentication).

Run it on a schedule rather than on every push. Nothing about a commitment's term changes between
two commits, and a daily or weekly job gives the thirty days of warning the gate is asking for.

## Providers that cannot be checked

Commitment expiry is measured for AWS and Azure. Elsewhere the command names why and exits `0`:

```text
$ l4 commitments expiring --fail-within 30d --provider gcp
Commitment expiry is not measured for Google Cloud: the commitment lifecycle export is not enabled.
Nothing checked.
$ echo $?
0
```

> **Note**
>
> Exiting `0` there is deliberate. A gate that cannot measure must not fail a build for ever. The line says nothing was checked, so an unmeasurable provider is visible in the log rather than silently passing.

## Errors

| Exit | Symptom                                               | Cause                                                                      |
| ---- | ----------------------------------------------------- | -------------------------------------------------------------------------- |
| `2`  | `N commitment(s) lapse within <w>`                    | The gate fired. This is the intended result, not a fault                   |
| `4`  | Authentication required                               | No token resolvable; run `l4 auth login` or set `LEVELFOUR_TOKEN`          |
| `1`  | `invalid window`                                      | `--within` or `--fail-within` was not a number followed by `d`, `w` or `m` |
| `0`  | `Commitments are not yet available for your account.` | The API does not serve the commitments routes yet                          |

## Related

* [`l4 commitments renewal`](/cli/commands/commitments/renewal): what to buy once the gate fires
* [`l4 commitments`](/cli/commands/commitments): the rest of the group
* [`l4 diff`](/cli/commands/diff#ci-gate): the other gate in this CLI
* [CI/CD integration](/guides/ci-cd): wiring a gate into a pipeline
