Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Codex subscription quota monitor

This is a self-hosted Python 3.11+ monitor for the subscription rate limits and reset times exposed by the local Codex app-server. It sends a Telegram GIF only after an early reset is confirmed by two equivalent polls.

The Platform Usage/Costs Admin API is not a source for Codex subscription limits. The monitor does not use an Admin API key, private HTTP endpoint, or OAuth token. It starts the installed codex CLI as the authenticated service user and asks its local app-server for account/rateLimits/read. The existing Codex login/auth source must therefore be available under the configured CODEX_HOME on the host running the service. Credentials are never placed in source, fixtures, SQLite, logs, or examples.

Behavior

Each successful response is normalized into <limit_id>:primary and <limit_id>:secondary windows. The monitor stores normalized snapshots so it can compare the last advertised reset time with the next sample. Raw JSON, auth material, and unknown provider fields are discarded.

  • The first sample establishes a baseline and cannot alert.
  • A scheduled/ordinary reset is ignored when it happens at or after the prior advertised reset, within the configured skew, or when an early reset cannot be proven.
  • A manual reset is ignored when rateLimitResetCredits.availableCount decreases. --ignore-next-reset and the restricted Telegram command are the fallback when credit data is unavailable.
  • An early reset requires a material used-percent drop and remaining-percent rise, a forward reset-window change, known reset-credit data, and two consecutive observations of the same window and previous advertised reset. The provider may adjust the newly advertised reset time between those observations. Primary and secondary anomalies in the same incident are grouped into one alert.
  • A pending candidate is cancelled by an ordinary, manual, unknown, or materially different observation. Repeated polls deduplicate one incident; a later reset with a new baseline advertised reset or window identity is a new incident.

The normalized snapshot remains in SQLite after an ordinary or manual reset so the next comparison has a correct baseline. Those resets do not create rows in reset_events and do not send Telegram alerts.

Local setup

Runtime dependencies are Python standard library modules only. A virtualenv is optional for local use and is used by the deployment script.

python3.11 -m venv .venv
. .venv/bin/activate
python -m pip install --no-deps -e .
cp .env.example .env.private
chmod 600 .env.private

Edit .env.private with a text editor. It contains placeholders only until you replace them locally; never commit it. The file is not auto-loaded, so run the monitor with an environment file explicitly, for example:

set -a
. ./.env.private
set +a
python -m quota_monitor --status
python -m quota_monitor --once

Before --once, verify that the same user can run the installed Codex CLI and that its authenticated login is under CODEX_HOME (or the CLI's normal default home). Do not put a Codex OAuth token or Platform Admin key in the monitor env file. The CODEX_BIN default is codex; CODEX_HOME and the provider timeout are configurable.

Useful commands:

python -m quota_monitor --help
python -m quota_monitor --status
python -m quota_monitor --ignore-next-reset
python -m quota_monitor --once
python -m unittest discover -s tests -v

--ignore-next-reset writes a durable one-shot marker and does not contact Codex. It is consumed only when a material reset observation occurs.

Telegram setup

Create a bot with BotFather. In standalone same-user mode, keep the private environment file mode 0600. In systemd mode, use /etc/quota-monitor/quota-monitor.env with owner root, group quota-monitor, and mode 0640; mode 0600 would prevent the service user from reading it. The default target is the documented placeholder @codexresets; verify that this username is the actual channel destination, or set TELEGRAM_CHAT_ID to the real channel/group ID. Do not infer a Bot API chat ID from a screenshot or another peer identifier.

The bot must be added to the target channel/group with permission to post animations. If command handling is enabled, set TELEGRAM_ALLOWED_USER_IDS to the numeric IDs of trusted operators. An empty allow-list is fail-closed, so arbitrary channel members cannot mark a reset ignored. /ignore-next-reset and /status are fetched with optional getUpdates polling; they are disabled by default.

TELEGRAM_ANIMATION_PATH can point to a private valid GIF. Without it, the monitor decodes the bundled two-frame placeholder only when an alert is sent and writes it to the state directory. Telegram request failures are logged as redacted error codes, leave the event unsent, and are retried on later polls.

VPS deployment

The supported layout uses a dedicated quota-monitor service user and keeps both the SQLite state and the Codex auth home under /var/lib/quota-monitor. This lets systemd grant the service user access to the login without running the monitor as root.

  1. Install Python 3.11+, the Codex CLI, and its documented app-server support on the VPS. Confirm the CLI version supports app-server --stdio and account/rateLimits/read.

  2. Create or use a dedicated service account. Copy or establish the Codex login for that account using the Codex-supported login flow, with CODEX_HOME=/var/lib/quota-monitor/codex-home. Do this interactively as the service user; do not pass a token on a command line or put it in a unit file. If the installed Codex auth model cannot safely be used by a dedicated service user, keep the auth home readable/writable only by the chosen service account and adjust the unit's User/Group deliberately; do not fall back to root credentials.

  3. Copy this repository to /opt/quota-monitor and run the installer as root:

    sudo ./deploy/install.sh

    The installer enforces the fixed paths used by the unit (/opt/quota-monitor, /etc/quota-monitor, /var/lib/quota-monitor, and the quota-monitor service account); non-default path overrides are rejected. It never takes secrets as command-line arguments or prints environment values.

  4. Create the private env file with an editor, then lock it down. The service group needs read access; the Bot token is still never passed as an argument:

    sudo install -o root -g quota-monitor -m 0640 /dev/null /etc/quota-monitor/quota-monitor.env
    sudoedit /etc/quota-monitor/quota-monitor.env
    sudo systemctl restart quota-monitor

    Use values equivalent to .env.example, including: CODEX_HOME=/var/lib/quota-monitor/codex-home and QUOTA_MONITOR_STATE_DIR=/var/lib/quota-monitor/state.

  5. Confirm the unit and state without exposing its environment:

    sudo systemctl enable --now quota-monitor
    sudo systemctl status quota-monitor --no-pager
    sudo journalctl -u quota-monitor -n 100 --no-pager
    sudo -u quota-monitor sh -c 'set -a; . /etc/quota-monitor/quota-monitor.env; set +a; exec /opt/quota-monitor/.venv/bin/python -m quota_monitor --status'
    sudo -u quota-monitor sh -c 'set -a; . /etc/quota-monitor/quota-monitor.env; set +a; exec /opt/quota-monitor/.venv/bin/python -m quota_monitor --once'
    sudo -u quota-monitor sh -c 'set -a; . /etc/quota-monitor/quota-monitor.env; set +a; exec /opt/quota-monitor/.venv/bin/python -m quota_monitor --ignore-next-reset'

The unit is in `deploy/systemd/quota-monitor.service`. It uses
`ProtectSystem=strict`, `ProtectHome=read-only`, `PrivateTmp`, no new
privileges, a dedicated user, and a restricted writable state path. Review the
Codex CLI's auth refresh behavior before enabling stricter filesystem settings;
the service user's `CODEX_HOME` must remain writable if the CLI refreshes it.

## State and safe rotation

SQLite contains normalized samples, durable baseline/candidate/ignore state,
and only normalized unexpected-reset event fields. It must not contain a Bot
token, Admin key, OAuth token, or raw provider response. Stop the service (or
use SQLite's online backup facility) before copying the database. Back up the
database and deployment configuration separately; the bundled GIF can be
recreated and the Codex auth home is a separate sensitive credential store.

To rotate the Telegram token, use BotFather, edit the private environment file
with `sudoedit`, verify mode/ownership, and restart the unit. Do not include the
new token in shell history, `systemctl` arguments, journal output, issue
reports, or backups that do not need it. If the token is ever exposed, revoke
it before updating the file.

## Limitations and protocol compatibility

The provider intentionally isolates all app-server protocol assumptions in
`src/quota_monitor/provider.py`. It ignores JSONL notifications, matches JSON-
RPC response IDs, accepts the camelCase fields used by current app-server
responses plus documented snake-case equivalents, and raises a typed error for
timeouts, malformed output, missing windows, or an unavailable binary. If a
future Codex CLI changes the method or response shape, update that module and
its tests; do not add undocumented private HTTP calls or substitute the Admin
API. No network, Codex CLI, or Telegram service is required by the test suite.

About

Telegram alerts for Codex subscription quota resets

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages