v0.17.0
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/machineendpoint, writes the new name to
machine.local.toml, and updates the badge immediately on success.
Empty names are rejected (400). (#766) -
moadim uninstallnow clears the managed crontab blocks (both
# BEGIN MOADIM-ROUTINESand# BEGIN MOADIM) in addition to removing the OS
service, socronstops 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 /healthnow reports adependenciessection (currently{"tmux": bool})
so the UI/CLI can detect when thetmuxruntime dependency is missing, and the
daemon logs awarn!at startup naming the missing binary.tmuxis a hard
dependency — every routine agent launches inside a tmux session — but its
absence was previously unchecked and undocumented, so a host withouttmux
made scheduled routine runs silently no-op. Detection reuses the existing
PATH probe (tmux_available_in/tmux_available) (#187). -
GET /routines.icsaccepts 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 (still200 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 samecreated_at. The block is built from aHashMap,
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 needlesscrontab -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
▶ RUNbutton that fires the job's trigger endpoint
(POST /api/v1/routines/{id}/triggeror/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_textnow normalises both bare\rand CRLF sequences to an escaped newline (\n)
before emitting them into aTEXTproperty value, satisfying RFC 5545 §3.3.11 which forbids
raw CR characters in content lines. Closes #181. -
A
fmt + clippyCI workflow (.github/workflows/lint.yml) that mirrors the
pre-push hook (cargo fmt --check,cargo clippy -- -D warnings) on every PR
and push tomain, 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 loadsAGENTS.mdas a memory/context file, so the disclosure is honored
exactly as it was fromCLAUDE.md. User-authored agent configs that omit
instructions_filestill fall back to the historicalCLAUDE.mddefault. -
The request logger now records
GET /healthatdebuginstead ofinfo.
The web UI polls/healthcontinuously, so at the defaultinfolevel those
two-lines-per-poll entries dominateddaemon.log(thousands of lines a day on
an otherwise idle daemon) and buried every other request. Health polls remain
visible underRUST_LOG=debug; all other requests still log atinfo. -
On first run (no
MOADIM_MACHINEenv var and nomachine.local.toml), the
daemon now auto-generates a unique machine name (machine-{8hex}) and writes
it tomachine.local.tomlrather than silently falling back to the system
hostname. Awarn!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_orlint 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.
croneraccepts
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 --jsonnow reports the real boundaddress. Under a
MOADIM_BIND_ADDRoverride,stop --jsonemitted the hardcoded default
127.0.0.1:5784whilestatus --jsonreported the actual address, so the two
--jsonobjects disagreed despite the documented "identical shape" contract.
stop_jsonnow usesbind_addr()likestatus_json. Added a regression test
plus a guard assertingstatus/stopproduce the same object. -
The pid file is now reconciled against process liveness before it is reported
or acted on. After akill -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 -0probe on Unix) as absent and cleans the stale file up best-effort.
status/stop --jsontherefore emitpid: nullconsistently with
running: falseinstead of a dead-or-PID-reused number, andrestartnever
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.mdfor 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 asAgentLoadError::Unreadablerather
thanAgentLoadError::Missing. Previously, anyread_to_stringfailure was
silently mapped toMissing, causingvalidate_agentto accept the broken
config (it toleratesMissingfor configs not yet created), leaving a
green-dot routine that silently never fires. The newUnreadablevariant is
rejected at create/update time with aBadRequest, so the operator learns the
real cause immediately. (#445) -
Loading a routine whose
routine.tomlis unparsable or missing a required
field (title, schedule, or agent) now logs awarnnaming the directory,
instead of silently dropping the routine from the store, UI, API, and crontab
with no trace. Directories with noroutine.tomlare 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-dirtysuffix on its short
SHA (e.g.a1b2c3d-dirty) inmoadim --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 installnow 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 callingwait(), so every trigger leaked one
zombie for the daemon's lifetime. A newutils::process::spawn_and_reaputility
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 exits0on success, errors with
no routine with id <id>on a404, and printsmoadim is not running
(exit3) when no server is reachable, matching thestatus/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/machineendpoint
returns the daemon's resolved machine name.GET /routinesandGET /cron-jobsnow accept a
local_only=truequery parameter that filters the response to entries targeting the current
machine. The MCPlist_routinesandlist_cron_jobstools gain the same parameter, defaulting
totrueso 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.