gastownhall/beads · error
no timeout set
Error message
no timeout set
What it means
Returned by checkTimer when a timer gate has Timeout == 0 (cmd/bd/gate.go:1166). A timer gate without a configured duration can never expire, so evaluation fails with 'no timeout set' and the reason 'timer gate without timeout configured'.
Source
Thrown at cmd/bd/gate.go:1166
// Evaluate status
switch status.State {
case "MERGED":
return true, false, fmt.Sprintf("PR '%s' was merged", status.Title), nil
case "CLOSED":
return false, true, fmt.Sprintf("PR '%s' was closed without merging", status.Title), nil
case "OPEN":
return false, false, fmt.Sprintf("PR '%s' is still open", status.Title), nil
default:
return false, false, fmt.Sprintf("PR '%s' state: %s", status.Title, status.State), nil
}
}
// checkTimer checks a timer gate for expiration
// Note: timers resolve but never escalate (escalated is always false by design)
func checkTimer(gate *types.Issue, now time.Time) (resolved, escalated bool, reason string, err error) { //nolint:unparam // escalated intentionally always false
if gate.Timeout == 0 {
return false, false, "timer gate without timeout configured", fmt.Errorf("no timeout set")
}
expiresAt := gate.CreatedAt.Add(gate.Timeout)
if now.After(expiresAt) {
expired := now.Sub(expiresAt).Round(time.Second)
return true, false, fmt.Sprintf("timer expired %s ago", expired), nil
}
remaining := expiresAt.Sub(now).Round(time.Second)
return false, false, fmt.Sprintf("expires in %s", remaining), nil
}
// issueGetter is the one storage method checkBeadGate needs, split out so
// tests can fake the lookup without standing up a Dolt store.
type issueGetter interface {
GetIssue(ctx context.Context, id string) (*types.Issue, error)
}
View on GitHub (pinned to 71377f2769)
Solutions
- Set a timeout on the gate (e.g. `bd update <gate-id>` with a valid duration, or recreate with `--timeout 30m`)
- Verify the gate type is really 'timer' — other gate types don't need Timeout
- Check creation code/flags so the duration is parsed and assigned before persisting
- Validate Timeout > 0 in your gate-creation wrapper before saving
Example fix
// before bd create "wait for CI" --type timer // no timeout // after bd create "wait for CI" --type timer --timeout 30m
Defensive patterns
Strategy: validation
Validate before calling
if gate.Type == "timer" && gate.Timeout <= 0 {
return errors.New("timer gate requires a positive timeout")
} Try / catch
if _, _, _, err := checkTimer(gate, time.Now()); err != nil && err.Error() == "no timeout set" {
// fix the gate config: set --timeout and re-create/update the gate
} Prevention
- Always pass --timeout when creating timer gates
- Validate Timeout > 0 in gate-creation helpers
- Confirm durations parse correctly (e.g. '30m' not '30')
When it happens
Trigger: A gate of type timer is created or evaluated with gate.Timeout left at its zero value — e.g. created without a --timeout flag or with a timeout that failed to parse into a duration.
Common situations: Manual gate creation forgetting the timeout field, YAML/JSON config omitting 'timeout', a type mismatch causing the duration to silently default to 0, programmatic creation passing an unset time.Duration.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- server: NewDoltServer: doltBinExec is required
- server: NewDoltServer: rootDir is required
- server: NewDoltServer: configPath is required
- ErrPrefixMismatch
- invalid key %q: expected storage-class.<issue-type> (e.g. st
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b090d4532d4e472b.
Report an issue: GitHub.