feat(jobs): persist lastResult and lastRanAt per job in state#182
Open
Nibbler1250 wants to merge 1 commit intomoazbuilds:masterfrom
Open
feat(jobs): persist lastResult and lastRanAt per job in state#182Nibbler1250 wants to merge 1 commit intomoazbuilds:masterfrom
Nibbler1250 wants to merge 1 commit intomoazbuilds:masterfrom
Conversation
Resolves moazbuilds#175. Adds two optional fields to each entry in state.json so the statusline / heartbeat prompt / external monitors can see what each job last did without re-executing it. - src/statusline.ts: extend StateData.jobs[] type with optional lastResult: "ok" | "error" | "skipped" and lastRanAt: number (ms). - src/commands/start.ts: - declare in-memory jobLastResult Map (resets on daemon restart, like jobRetryState). - record outcome in the runJob() then-handler right after the run completes — same place jobRetryState is touched. - in updateState(), spread the recorded last-result fields into each job entry so they land in state.json on the next 60s tick. Fields are additive (absent until a job has run at least once), no breaking change to existing consumers, no new disk I/O beyond what updateState() already does. Source: requested by @Nibbler1250 in moazbuilds#14 (Mega-Post).
TerrysPOV
requested changes
May 3, 2026
Collaborator
TerrysPOV
left a comment
There was a problem hiding this comment.
Found 2 issues:
- Daemon crashes at startup when any jobs are configured — updateState() is called immediately at line 770, but const jobLastResult (which the updated updateState() body now reads via jobLastResult.get(job.name)) is declared at line ~779. const variables are in the temporal dead zone until their declaration is reached; accessing one before that throws ReferenceError: Cannot access 'jobLastResult' before initialization. Any user with jobs configured will see the daemon die on startup. Fix: move the jobLastResult declaration above the updateState() call (alongside or before jobRetryState).
claudeclaw/src/commands/start.ts
Lines 750 to 757 in a7dd0d0
claudeclaw/src/commands/start.ts
Lines 776 to 780 in a7dd0d0
- "skipped" is declared in the type but never written — The type union in statusline.ts includes "skipped", and jobLastResult is typed to accept it, but the only write path sets "ok" or "error" from r.exitCode. The natural trigger for "skipped" — jobs bypassed because isRateLimited() returns true — has no jobLastResult.set() call. Any consumer checking lastResult === "skipped" to detect rate-limit suspensions will never see it. Fix: set { result: "skipped", ranAt: Date.now() } inside the isRateLimited() branch of the setInterval, once per job that was due to fire.
Lines 8 to 12 in a7dd0d0
Please effect these changes, commit and resubmit.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #175.
Adds two optional fields to each job entry in
state.jsonso the statusline / heartbeat prompt / external monitors can see what each job last did without re-executing it.Changes
src/statusline.ts— extendStateData.jobs[]type:src/commands/start.ts— three small additions:jobLastResultMap alongside the existingjobRetryStateMap (same lifecycle: resets on daemon restart, no disk persistence).runJob().then()handler, right after the run completes (next to wherejobRetryStateis updated), record the outcome:updateState(), spread the recorded fields into each job entry so they land instate.jsonon the next 60-second tick:Properties
updateState()60s tick.Map<string, { result, ranAt }>in memory.jobRetryState: in-memory only, drops on daemon restart (no stale debt across restarts, consistent with how retry state already works in this file).Notes on
"skipped"I included
"skipped"in the union type for forward compatibility (e.g. a future heartbeat could mark a job skipped due to rate-limit / disabled / pause), but this PR doesn't writeskippedanywhere yet. Happy to drop that variant if you'd rather keep the surface tight to"ok" | "error"for now.Build
bun build src/index.ts --target=bun→ 0.42 MB, clean.bunx tsc --noEmit→ no new errors. The two pre-existing errors atstart.ts:442/:447(forwardToTelegram/forwardToDiscordarity mismatch) live onmasteralready; I left them alone.Source
Originally requested by myself in #14 (Mega-Post) — thanks @TerrysPOV for breaking it out into its own ticket.