feat: per-job command timeout with global default
Add an optional per-job run timeout following the overlap_policy inherit pattern: Job.TimeoutSeconds (0 = inherit) resolves against a new Config.DefaultTimeoutSeconds (default 30s), replacing the hard-coded 30s guard in runner.RunJob. - domain/storage: new fields, default 30, load-time normalization - runner: RunJob takes an explicit timeout; StartOnly stays untimed so it keeps measuring launch latency only - app: effectiveTimeout resolves under mu into runEnv, threaded to runJob; seam signature and validation updated; DisplayTimeout helper - ui: Timeout entry in the job dialog, Default timeout in Settings, and a Timeout row in the details panel - tests + docs (ARCHITECTURE, STANDARDS, ROADMAP, CHANGELOG) updated; version bumped to 0.12.0 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+16
-2
@@ -87,8 +87,9 @@ flowchart LR
|
||||
|
||||
5. Command execution:
|
||||
`runner.RunJob` builds the platform-specific invocation, executes the
|
||||
command through the platform shell, captures stdout and stderr, writes one
|
||||
timestamped `.log` file, and returns a `domain.RunRecord` containing
|
||||
command through the platform shell under the caller-supplied timeout, captures
|
||||
stdout and stderr, writes one timestamped `.log` file, and returns a
|
||||
`domain.RunRecord` containing
|
||||
`DurationMS` (wall-clock milliseconds from start to finish; for `StartOnly`
|
||||
fire-and-forget jobs it measures launch latency — the time to spawn the
|
||||
process — since there is no exit to wait for).
|
||||
@@ -128,6 +129,19 @@ in flight increments `JobRuntime.PendingRuns`. When the current run finishes,
|
||||
`executeRun` drains the counter by starting one deferred run per completion until
|
||||
`PendingRuns` reaches zero.
|
||||
|
||||
### Per-job command timeout
|
||||
|
||||
`domain.Job` carries a `TimeoutSeconds` field (`json:"timeout_seconds,omitempty"`),
|
||||
following the same inherit pattern as the overlap policy. `0` means inherit the
|
||||
global `Config.DefaultTimeoutSeconds` (default **30**); a positive value overrides
|
||||
it for that job alone. `app.Service.effectiveTimeout` resolves the effective
|
||||
duration under `mu` and `startRunLocked` snapshots it into `runEnv.timeout`.
|
||||
`runner.RunJob(ctx, job, trigger, logsDir, timeout)` takes the resolved duration
|
||||
as an argument, so the runner stays ignorant of the global config: it applies the
|
||||
timeout via `context.WithTimeout` and reports `Timed out after <timeout>` on
|
||||
expiry. `StartOnly` jobs run on the untimed context and so measure launch latency
|
||||
only, unaffected by the run timeout.
|
||||
|
||||
### Run-time statistics
|
||||
|
||||
`domain.JobRuntime` holds a rolling aggregate updated after each run:
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
|
||||
All notable GoSentry changes are recorded in this file.
|
||||
|
||||
## 0.12.0 - 2026-07-25
|
||||
|
||||
**Per-job command timeout:**
|
||||
- Each job may now set its own run timeout (seconds) in the job dialog; leaving
|
||||
it empty inherits a new **Default timeout** in Settings (default 30s), the same
|
||||
inherit pattern as the overlap policy. The details panel shows the effective
|
||||
value, marking inherited jobs as `(global default)`.
|
||||
- The formerly hard-coded 30s guard in `runner.RunJob` is now the configurable
|
||||
default. `StartOnly` fire-and-forget jobs remain unaffected by the run timeout,
|
||||
continuing to measure launch latency only.
|
||||
|
||||
## 0.11.5 - 2026-07-01
|
||||
|
||||
**Quality and documentation polish:**
|
||||
|
||||
@@ -5,18 +5,6 @@ Completed work is recorded in [CHANGELOG.md](CHANGELOG.md), not here.
|
||||
|
||||
## Open Items
|
||||
|
||||
### Per-job command timeout
|
||||
|
||||
`runner.RunJob` applies a fixed **30s** timeout to every command (`commandTimeout`
|
||||
in `src/runner/runner.go`). Long-running or interactive scripts need a longer
|
||||
limit; quick health checks may need a shorter one.
|
||||
|
||||
Add an optional per-job timeout (seconds) on `domain.Job`, with a global default
|
||||
in `gosentry.json` for jobs that leave the field empty — the same inherit pattern
|
||||
as `overlap_policy`. Wire the value through `RunJob`; expose it in the job dialog
|
||||
and Settings; validate on save. `StartOnly` jobs should keep measuring launch
|
||||
latency only and remain unaffected by the run timeout.
|
||||
|
||||
### Window size persistence *(frozen)*
|
||||
|
||||
Window size is currently **not** saved on quit or close. Saving was disabled
|
||||
|
||||
+4
-3
@@ -17,12 +17,13 @@ in [ARCHITECTURE.md](ARCHITECTURE.md); test conventions in [TESTS.md](TESTS.md).
|
||||
- `RunNow` is allowed during global pause and for disabled jobs.
|
||||
- Sequential mode runs jobs FIFO by order in `jobs.json`.
|
||||
- Scheduler tick is 1s — sub-second `@every` intervals are not supported.
|
||||
- Command timeout is 30s globally.
|
||||
- Command timeout defaults to 30s globally and is overridable per job
|
||||
(`Job.TimeoutSeconds`, 0 = inherit `Config.DefaultTimeoutSeconds`).
|
||||
- **History tab is session-only.** `JobRuntime.Logs` exists only in memory for the
|
||||
current process. Log files on disk feed aggregate statistics via `SeedStats`
|
||||
only. See [ARCHITECTURE.md](ARCHITECTURE.md).
|
||||
|
||||
## Out of scope
|
||||
|
||||
Larger or blocked work is tracked in [ROADMAP.md](ROADMAP.md) (per-job timeout,
|
||||
window size persistence, History column filters, CI coverage gate).
|
||||
Larger or blocked work is tracked in [ROADMAP.md](ROADMAP.md) (window size
|
||||
persistence, History column filters, CI coverage gate).
|
||||
|
||||
Reference in New Issue
Block a user