Skip to content

v0.17.0

Choose a tag to compare

@github-actions github-actions released this 29 Jun 19:47
1f89594

Added

  • Machine name badge in the header: the resolved @ <name> is shown as a
    clickable chip. Clicking it opens a rename dialog that calls the new
    PUT /api/v1/machine endpoint, writes the new name to
    machine.local.toml, and updates the badge immediately on success.
    Empty names are rejected (400). (#766)

  • moadim uninstall now clears the managed crontab blocks (both
    # BEGIN MOADIM-ROUTINES and # BEGIN MOADIM) in addition to removing the OS
    service, so cron stops firing routines/jobs against a daemon you uninstalled.
    The routines block is cleared first because its marker is a superstring of the
    cron-jobs marker (avoids the #324 collision). Best-effort and idempotent — a
    crontab with no managed block, no crontab at all, or a failed service-removal
    step still completes the cleanup — and it reports how many managed entries were
    removed. (#380)

  • GET /health now reports a dependencies section (currently {"tmux": bool})
    so the UI/CLI can detect when the tmux runtime dependency is missing, and the
    daemon logs a warn! at startup naming the missing binary. tmux is a hard
    dependency — every routine agent launches inside a tmux session — but its
    absence was previously unchecked and undocumented, so a host without tmux
    made scheduled routine runs silently no-op. Detection reuses the existing
    PATH probe (tmux_available_in / tmux_available) (#187).

  • GET /routines.ics accepts an optional ?routine=<id> query param that
    scopes the feed to a single routine, so a calendar client can subscribe to one
    routine's fire times instead of the firehose of every routine on the host. The
    filtered calendar is named after the routine (X-WR-CALNAME); an unknown or
    disabled id yields a well-formed empty calendar (still 200 text/calendar).
    Without the param the feed is unchanged — every enabled routine (#263).

  • The generated routines crontab block is now deterministic when several
    routines share the same created_at. The block is built from a HashMap,
    whose iteration order is unspecified, so tied routines previously emitted in
    an arbitrary, run-to-run order — churning the block across syncs and defeating
    the idempotency guard, which forced a needless crontab - rewrite that
    mutates the user's live crontab. Ties are now broken on the stable routine id.

  • UI overview: "▶ RUN" quick-trigger button in the Upcoming Runs table.
    Each row in the UPCOMING RUNS table on the Overview page now carries a
    ▶ RUN button that fires the job's trigger endpoint
    (POST /api/v1/routines/{id}/trigger or /api/v1/cron-jobs/{id}/trigger)
    without leaving the page. A toast confirms success or surfaces the error.
    Implements the "quick actions" best practice from CI/CD operations dashboards
    (Cronitor, Temporal, GitHub Actions) where operators can fire jobs directly
    from the at-a-glance view.

  • iCal feed: carriage returns in routine titles/prompts no longer corrupt content lines.
    escape_text now normalises both bare \r and CRLF sequences to an escaped newline (\n)
    before emitting them into a TEXT property value, satisfying RFC 5545 §3.3.11 which forbids
    raw CR characters in content lines. Closes #181.

  • A fmt + clippy CI workflow (.github/workflows/lint.yml) that mirrors the
    pre-push hook (cargo fmt --check, cargo clippy -- -D warnings) on every PR
    and push to main, so style/lint regressions are caught in review without
    relying on local hooks.

Documentation

  • Documented the required external binaries (tmux, crontab) under a new
    Prerequisites section in the README (#187).

Changed

  • The built-in Claude agent now reads its project instructions from AGENTS.md,
    the same file Codex uses, unifying the moadim-managed system prompt and
    routine-origin disclosure onto a single instructions file across agents. Claude
    Code loads AGENTS.md as a memory/context file, so the disclosure is honored
    exactly as it was from CLAUDE.md. User-authored agent configs that omit
    instructions_file still fall back to the historical CLAUDE.md default.

  • The request logger now records GET /health at debug instead of info.
    The web UI polls /health continuously, so at the default info level those
    two-lines-per-poll entries dominated daemon.log (thousands of lines a day on
    an otherwise idle daemon) and buried every other request. Health polls remain
    visible under RUST_LOG=debug; all other requests still log at info.

  • On first run (no MOADIM_MACHINE env var and no machine.local.toml), the
    daemon now auto-generates a unique machine name (machine-{8hex}) and writes
    it to machine.local.toml rather than silently falling back to the system
    hostname. A warn! log names the generated value and points to
    moadim machine set <name> to override it. If the write fails the hostname
    fallback is preserved. Closes #762.

  • Enabled the clippy::map_unwrap_or lint and fixed the violations
    (map(...).unwrap_or(...)map_or(...)). No behavior change.

Fixed

  • iCal feeds now summarize long routine prompts in the DESCRIPTION field (first line, truncated to 120 chars) instead of embedding the full prompt, keeping calendar entries readable. (#185)

  • 6-field cron expressions no longer silently fail to fire. croner accepts
    6-field (sec min hour dom month dow) and 7-field expressions, but the OS
    crontab only understands 5 fields. Both forms are now normalised to 5 fields
    by dropping the leading seconds (and, for 7-field, the trailing year) before
    the expression is stored or written to the crontab. Previously a 6-field
    string was written verbatim, making the job malformed and silently inactive.
    Closes #183.

  • moadim stop --json now reports the real bound address. Under a
    MOADIM_BIND_ADDR override, stop --json emitted the hardcoded default
    127.0.0.1:5784 while status --json reported the actual address, so the two
    --json objects disagreed despite the documented "identical shape" contract.
    stop_json now uses bind_addr() like status_json. Added a regression test
    plus a guard asserting status/stop produce the same object.

  • The pid file is now reconciled against process liveness before it is reported
    or acted on. After a kill -9, panic, OOM kill, or power loss the graceful
    shutdown path never runs, so the pid file lingers with a now-dead PID.
    read_pid_file() now treats a recorded PID that is not a live process
    (kill -0 probe on Unix) as absent and cleans the stale file up best-effort.
    status/stop --json therefore emit pid: null consistently with
    running: false instead of a dead-or-PID-reused number, and restart never
    force-kills a stale PID. (#315)

  • The daemon now writes its managed system prompt and routine-origin disclosure to the agent's designated instructions file (AGENTS.md for Codex). Previously the Codex agent received the disclosure via a separate mechanism. (#152)

  • An agent config that exists on disk but cannot be read (due to a permissions
    error or path collision) is now reported as AgentLoadError::Unreadable rather
    than AgentLoadError::Missing. Previously, any read_to_string failure was
    silently mapped to Missing, causing validate_agent to accept the broken
    config (it tolerates Missing for configs not yet created), leaving a
    green-dot routine that silently never fires. The new Unreadable variant is
    rejected at create/update time with a BadRequest, so the operator learns the
    real cause immediately. (#445)

  • Loading a routine whose routine.toml is unparsable or missing a required
    field (title, schedule, or agent) now logs a warn naming the directory,
    instead of silently dropping the routine from the store, UI, API, and crontab
    with no trace. Directories with no routine.toml are still skipped quietly.
    (#530)

  • Build provenance now marks a dirty working tree. A binary built from a tree
    with uncommitted changes to tracked files gets a -dirty suffix on its short
    SHA (e.g. a1b2c3d-dirty) in moadim --version, GET /api/v1/health, and the
    MCP provenance, instead of misreporting a clean SHA that doesn't match its
    source. A pristine checkout is unchanged, and the "unknown" (no-git) fallback
    is preserved. (#491, follow-up to #367)

  • macOS: TCC "administer your computer" dialog no longer appears during background runs.
    moadim install now proactively sends a harmless Apple Event to System Events so macOS
    prompts for the Automation permission once, while the user is at the terminal. After clicking
    OK the grant is remembered permanently; the background daemon never triggers the dialog again.
    A hint line is printed before the prompt so users know what to expect. Closes #730.

  • Trigger-spawned processes are now reaped so the daemon no longer leaks zombie
    (<defunct>) entries.
    Both the routine trigger (POST /routines/{id}/trigger)
    and the cron-job trigger (POST /cron-jobs/{id}/trigger) previously dropped
    the spawned child handle without calling wait(), so every trigger leaked one
    zombie for the daemon's lifetime. A new utils::process::spawn_and_reap utility
    spawns the command and hands the child to a detached thread that reaps it.
    (#212)

Added

  • moadim trigger <id> triggers a routine to run immediately from the terminal,
    outside its schedule — the same on-demand run the REST API
    (POST /routines/{id}/trigger) and the MCP tool already expose. Prints
    triggered routine <id> and exits 0 on success, errors with
    no routine with id <id> on a 404, and prints moadim is not running
    (exit 3) when no server is reachable, matching the status/cleanup
    exit-code contract. (moadim run <id> is accepted as a hidden back-compat
    alias.)

  • UI: group-by dimension for the Routines table. A GROUP BY selector in the section
    toolbar lets operators partition the flat routine list into labelled sections by Agent,
    Machine, or Status (Enabled / Disabled), with a None option to restore the flat
    view. The selector only appears in Table view (hidden for Calendar and Day) and composes with
    the existing faceted filter and column-sort controls. Closes #733.

  • UI: clone/duplicate a routine. A ⧉ duplicate button on each routine row opens the
    create-routine form pre-filled with all fields from the source routine (schedule, agent, prompt,
    repositories, machines, TTL, enabled state). The title is automatically prefixed with
    "Copy of " (and the prefix is not doubled on repeated clones). Operators can adjust any field
    before saving; the result is a brand-new independent routine. Closes #715.

  • Local-machine filter for routines and cron jobs. A new GET /api/v1/machine endpoint
    returns the daemon's resolved machine name. GET /routines and GET /cron-jobs now accept a
    local_only=true query parameter that filters the response to entries targeting the current
    machine. The MCP list_routines and list_cron_jobs tools gain the same parameter, defaulting
    to true so MCP callers see local-first results. The UI routines and cron-jobs pages fetch the
    current machine on mount and default the existing machine facet filter to it; users can change
    the filter to "Any" to see all machines. Closes #726.