Symptom
Tower log shows the cron evaluator throwing on every tick for tasks whose condition references exitCode:
```
[WARN] Condition evaluation failed for 'Service Health Check': ReferenceError: exitCode is not defined
[WARN] Cron command failed for 'Service Health Check': FAILURES: ...
[INFO] Cron task 'Service Health Check' completed: failure
```
Repeats reliably every cron tick. The task ends up marked failed, so notification logic for failure cases also fires repeatedly, generating noise.
Root cause
packages/codev/src/agent-farm/servers/tower-cron.ts:293-297:
```typescript
export function evaluateCondition(condition: string, output: string): boolean {
// eslint-disable-next-line no-new-func
const fn = new Function('output', 'return ' + condition);
return !!fn(output);
}
```
The function takes only output as a parameter. Any cron task whose condition references additional variables (e.g. exitCode, stderr, durationMs) hits a ReferenceError at evaluation time.
At least one shipped task — Service Health Check — has a condition that references exitCode. The task author reasonably expected that the cron's full execution result (stdout + stderr + exit code) would be in scope, since the cron also captures all of them for logging.
Proposed fix
Two halves, both worth doing:
-
Widen the evaluator's scope to include exit code and stderr. Update evaluateCondition to accept (condition, { output, stderr, exitCode, durationMs }) and pass them all to the constructed function. Callers in tower-cron.ts:249 pass the full result instead of just trimmed stdout.
-
Document the available variables in the condition DSL. Update wherever cron-task condition syntax is documented (skeleton's team-update.yaml/cron docs, role docs, README) to enumerate exactly what's in scope so the next task author isn't guessing.
The fix is small (mechanical scope widening + caller update). Estimated < 100 LOC including the test.
Acceptance criteria
Suggested protocol
BUGFIX. The fix surface and root cause are both known; the scope is small and mechanical. No architectural decisions, no UI verification needed. Single builder.
Out of scope
- The wider "Tower terminals go non-responsive over time" hang. Filed as a separate
area/tower PIR.
- Restricting / sandboxing the condition DSL further (currently
new Function(...) evaluates arbitrary JS; that's a separate hardening conversation).
Symptom
Tower log shows the cron evaluator throwing on every tick for tasks whose
conditionreferencesexitCode:```
[WARN] Condition evaluation failed for 'Service Health Check': ReferenceError: exitCode is not defined
[WARN] Cron command failed for 'Service Health Check': FAILURES: ...
[INFO] Cron task 'Service Health Check' completed: failure
```
Repeats reliably every cron tick. The task ends up marked failed, so notification logic for failure cases also fires repeatedly, generating noise.
Root cause
packages/codev/src/agent-farm/servers/tower-cron.ts:293-297:```typescript
export function evaluateCondition(condition: string, output: string): boolean {
// eslint-disable-next-line no-new-func
const fn = new Function('output', 'return ' + condition);
return !!fn(output);
}
```
The function takes only
outputas a parameter. Any cron task whoseconditionreferences additional variables (e.g.exitCode,stderr,durationMs) hits aReferenceErrorat evaluation time.At least one shipped task —
Service Health Check— has aconditionthat referencesexitCode. The task author reasonably expected that the cron's full execution result (stdout + stderr + exit code) would be in scope, since the cron also captures all of them for logging.Proposed fix
Two halves, both worth doing:
Widen the evaluator's scope to include exit code and stderr. Update
evaluateConditionto accept(condition, { output, stderr, exitCode, durationMs })and pass them all to the constructed function. Callers intower-cron.ts:249pass the full result instead of just trimmed stdout.Document the available variables in the condition DSL. Update wherever cron-task condition syntax is documented (skeleton's
team-update.yaml/cron docs, role docs, README) to enumerate exactly what's in scope so the next task author isn't guessing.The fix is small (mechanical scope widening + caller update). Estimated < 100 LOC including the test.
Acceptance criteria
evaluateConditionaccepts an object of{ output, stderr, exitCode, durationMs }as the second parameter, exposes all four as variables in scope of the condition expression.tower-cron.ts:249passes the full result object.output-only conditions still work (back-compat),exitCode-based conditions work,stderr-based conditions work,durationMs-based conditions work, references to undeclared variables still throw (and the error is logged with the offending condition string for diagnosability).Service Health Checkexample) is verified to evaluate withoutReferenceErrorafter the fix.Suggested protocol
BUGFIX. The fix surface and root cause are both known; the scope is small and mechanical. No architectural decisions, no UI verification needed. Single builder.
Out of scope
area/towerPIR.new Function(...)evaluates arbitrary JS; that's a separate hardening conversation).