Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .claude/skills/afx/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,20 @@ afx cron enable <name> # Enable
afx cron disable <name> # Disable
```

There is NO `afx cron add` — create YAML files in `.af-cron/` directly.
There is NO `afx cron add` — create YAML files in `.af-cron/` directly:

```yaml
name: Service Health Check # required, unique per workspace
schedule: "*/15 * * * *" # required, cron expression (or @hourly/@daily/@startup)
command: ./health-check.sh # required, run via shell
message: "Health alert: ${output}" # required, ${output} = trimmed command output
condition: "exitCode != 0" # optional JS expression, see below
target: architect # optional, default architect
timeout: 30 # optional, seconds, default 30
enabled: true # optional, default true
```

`condition` is a JavaScript expression with two variables in scope: `output` (string — the command's trimmed output) and `exitCode` (number — 0 on success, the command's exit code on non-zero exit, 124 on timeout, -1 on spawn failure). With a `condition`, the message is delivered exactly when it evaluates truthy — including on failed runs (e.g. `exitCode != 0` alerts when the command fails). Without a `condition`, the message is delivered only when the command exits 0.

## Other commands

Expand Down
15 changes: 14 additions & 1 deletion codev-skeleton/.claude/skills/afx/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,20 @@ afx cron enable <name> # Enable
afx cron disable <name> # Disable
```

There is NO `afx cron add` — create YAML files in `.af-cron/` directly.
There is NO `afx cron add` — create YAML files in `.af-cron/` directly:

```yaml
name: Service Health Check # required, unique per workspace
schedule: "*/15 * * * *" # required, cron expression (or @hourly/@daily/@startup)
command: ./health-check.sh # required, run via shell
message: "Health alert: ${output}" # required, ${output} = trimmed command output
condition: "exitCode != 0" # optional JS expression, see below
target: architect # optional, default architect
timeout: 30 # optional, seconds, default 30
enabled: true # optional, default true
```

`condition` is a JavaScript expression with two variables in scope: `output` (string — the command's trimmed output) and `exitCode` (number — 0 on success, the command's exit code on non-zero exit, 124 on timeout, -1 on spawn failure). With a `condition`, the message is delivered exactly when it evaluates truthy — including on failed runs (e.g. `exitCode != 0` alerts when the command fails). Without a `condition`, the message is delivered only when the command exits 0.

## Other commands

Expand Down
36 changes: 36 additions & 0 deletions codev-skeleton/resources/commands/agent-farm.md
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,42 @@ afx tower status [options]

---

### afx cron

Manage scheduled tasks defined as YAML files in `.af-cron/` at the workspace root. The Tower scheduler loads these every tick, runs due commands, and delivers messages through the normal send pipeline.

```bash
afx cron list # List all cron tasks
afx cron status <name> # Check task status
afx cron run <name> # Run immediately
afx cron enable <name> # Enable
afx cron disable <name> # Disable
```

There is NO `afx cron add` — create YAML files in `.af-cron/` directly.

**Task YAML format:**

```yaml
name: Service Health Check # required, unique per workspace
schedule: "*/15 * * * *" # required, cron expression (or @hourly/@daily/@startup)
command: ./health-check.sh # required, run via shell
message: "Health alert: ${output}" # required, ${output} = trimmed command output
condition: "exitCode != 0" # optional JS expression, see below
target: architect # optional, default architect
timeout: 30 # optional, seconds, default 30
enabled: true # optional, default true
```

**Condition environment:** `condition` is a JavaScript expression evaluated with two variables in scope:

- `output` (string) — the command's trimmed output
- `exitCode` (number) — `0` on success, the command's exit code on non-zero exit, `124` on timeout, `-1` on spawn failure

With a `condition`, the message is delivered exactly when the expression evaluates truthy — including on failed runs, so `condition: "exitCode != 0"` alerts when the command fails. Without a `condition`, the message is delivered only when the command exits 0.

---

### afx db

Database debugging and maintenance commands.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
id: bugfix-1142
title: tower-cron-conditions-can-t-se
protocol: bugfix
phase: pr
plan_phases: []
current_plan_phase: null
gates:
pr:
status: pending
requested_at: '2026-07-06T09:56:04.513Z'
iteration: 1
build_complete: false
history: []
started_at: '2026-07-06T09:41:52.658Z'
updated_at: '2026-07-06T09:56:04.513Z'
pr_ready_for_human: true
36 changes: 36 additions & 0 deletions codev/resources/commands/agent-farm.md
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,42 @@ Displays local tower status plus cloud registration details: tower name, ID, con

---

### afx cron

Manage scheduled tasks defined as YAML files in `.af-cron/` at the workspace root. The Tower scheduler loads these every tick, runs due commands, and delivers messages through the normal send pipeline.

```bash
afx cron list # List all cron tasks
afx cron status <name> # Check task status
afx cron run <name> # Run immediately
afx cron enable <name> # Enable
afx cron disable <name> # Disable
```

There is NO `afx cron add` — create YAML files in `.af-cron/` directly.

**Task YAML format:**

```yaml
name: Service Health Check # required, unique per workspace
schedule: "*/15 * * * *" # required, cron expression (or @hourly/@daily/@startup)
command: ./health-check.sh # required, run via shell
message: "Health alert: ${output}" # required, ${output} = trimmed command output
condition: "exitCode != 0" # optional JS expression, see below
target: architect # optional, default architect
timeout: 30 # optional, seconds, default 30
enabled: true # optional, default true
```

**Condition environment:** `condition` is a JavaScript expression evaluated with two variables in scope:

- `output` (string) — the command's trimmed output
- `exitCode` (number) — `0` on success, the command's exit code on non-zero exit, `124` on timeout, `-1` on spawn failure

With a `condition`, the message is delivered exactly when the expression evaluates truthy — including on failed runs, so `condition: "exitCode != 0"` alerts when the command fails. Without a `condition`, the message is delivered only when the command exits 0.

---

### afx db

Database debugging and maintenance commands.
Expand Down
39 changes: 39 additions & 0 deletions codev/state/bugfix-1142_thread.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Builder thread — bugfix-1142

Issue #1142: tower-cron conditions can't see `exitCode` (ReferenceError every run) and failure runs can never deliver alerts.

## Investigate (2026-07-06)

Reproduced and confirmed root cause, all in `packages/codev/src/agent-farm/servers/tower-cron.ts`:

1. `evaluateCondition()` builds `new Function('output', 'return ' + condition)` — `exitCode` is not in scope, so `condition: "exitCode != 0"` throws `ReferenceError` every run (12,453 occurrences in my local `~/.agent-farm/tower.log`). The catch at the call site forces `shouldNotify = false`.
2. `runCommand()` rejects on any non-zero exit, so the exit code is never captured as data — `executeTask` classifies it as `result = 'failure'`.
3. Delivery gate is `if (shouldNotify && result === 'success')` — failure runs only log a WARN. "Alert me when this command fails" is inexpressible.

Doc surfaces for the condition environment (`output: string`, `exitCode: number`):
- `.claude/skills/afx/SKILL.md` + `codev-skeleton/.claude/skills/afx/SKILL.md` (`## afx cron` section, currently CLI-commands-only)
- `codev/resources/commands/agent-farm.md` + `codev-skeleton/resources/commands/agent-farm.md` (no cron section yet — will add one)

Fix design is prescribed in the issue (carry it as-is): capture `{output, exitCode}` in `runCommand` (resolve on plain non-zero exit, keep rejecting on `error.killed`/`error.signal`/spawn failure), pass `exitCode` as second param to conditions, deliver on condition-true regardless of success/failure split, keep no-condition tasks delivering only on exit 0, keep `last_result` semantics by exit code.

Scope: ~100 LOC in one source file + regression tests + docs. Well within BUGFIX. → PHASE_COMPLETE

## Fix (2026-07-06)

Implemented the prescribed design in `tower-cron.ts` (commit ecf294ae):
- `runCommand` now resolves `{ output, exitCode }`; plain non-zero exits resolve (exit code is data), only spawn failures / timeout-kills reject (exitCode -1 / 124 in the catch).
- `evaluateCondition(condition, output, exitCode = 0)` — both variables in Function scope.
- Delivery: with a condition, the condition alone decides (failure runs can deliver); without one, deliver only on exit 0 (unchanged). `last_result` semantics unchanged.
- 8 new regression tests (34 total in tower-cron.test.ts, all pass). Full suite: 3440 passed / 48 pre-existing skips.
- Docs: condition environment (`output`, `exitCode`) documented in the afx skill `## afx cron` section and a new `### afx cron` section in agent-farm.md — BOTH trees (codev/ + codev-skeleton/, and .claude/skills + skeleton skills).
- Root `pnpm build` passes.

## PR (2026-07-06)

- Porch checks (build + tests) passed; advanced to pr phase.
- PR #1143 opened: https://github.com/cluesmith/codev/pull/1143 (Fixes #1142).
- End-to-end semantics verified against built dist + real Node exec: non-zero exit → `code=3, killed=false, signal=null` (resolves as data); timeout → `code=null, killed=true, signal=SIGTERM` (rejects → 124). Matches the implementation's branch conditions exactly.
- CMAP note: `consult --protocol bugfix --type pr` needed explicit `--project-id bugfix-1142` (auto-detect hit "Multiple projects found" in the worktree).
- CMAP verdicts: gemini=APPROVE (HIGH), codex=APPROVE (HIGH), claude=APPROVE (HIGH). No key issues from any reviewer.
- CI on PR #1143: all 6 checks green, mergeable.
- Requested the `pr` gate via porch done; waiting for human approval.
Loading
Loading