Skip to content

feat: restart accounting on the Linux runtime - #134

Merged
passcod merged 14 commits into
mainfrom
claude/pr-133-implementation-yoa1va
Aug 1, 2026
Merged

feat: restart accounting on the Linux runtime#134
passcod merged 14 commits into
mainfrom
claude/pr-133-implementation-yoa1va

Conversation

@passcod

@passcod passcod commented Aug 1, 2026

Copy link
Copy Markdown
Member

Implements the plan from #133.

Seedling now records every container restart and derives crash_loop from the recorded rate. systemd still actions restarts on Linux; seedling keeps the books. Sub-threshold flapping — a container that crashes a couple of times a day forever, never exhausting StartLimitBurst inside its window — is no longer silent.

The two open decisions

The plan left two open; both are recorded in its ## Decided section (in history, since the plan is removed by this PR).

  • Threshold and window are operator-settable, not compiled in. Stored in restart_settings, read and changed through /restarts/settings/{get,set}, and re-read by the reconciler each tick so a change takes effect without a restart. Default is 5 recovery restarts within 30 minutes; threshold floors at 2, window_secs at 60.
  • Restart history is its own surface, /restarts/list, plus a per-instance summary folded into app.describe so the count is visible without going looking.

Cause, not actor

Following review: a restart record's cause is recovery or deliberate — why the restart happened, not which component performed it. On Linux the two coincide, since systemd actions recovery and seedling actions deploys, but only as a property of this platform. A runtime with no service supervisor performs both kinds, so a field naming the actor would classify everything there identically, leave the rate counting nothing, and kill the primary crash-loop trigger on the very runtime that made this requirement portable.

Spec first

docs/spec/runtime.md: new autonomous.restart.record, autonomous.restart.rate, autonomous.restart.rate.settings, gc.restarts; autonomous.restart.backoff becomes the Linux pacing mechanism rather than the definition of a crash loop; autonomous.restart.start-limit-hit is demoted to a secondary trigger; fault.crash-loop gains both triggers and must say which fired.

docs/spec/interface.md: restart.record, restart.list, restart.settings, and the app.describe amendment. docs/spec/web.md: routes.restarts.

All four specs are at 100% implementation coverage with zero stale references.

How restarts are observed

Polling container state cannot see a restart that completes inside one observe interval — the unit is active at both ends. So the runtime reads systemd's own NRestarts, which lives on org.freedesktop.systemd1.Service rather than the Unit interface already proxied, alongside ExecMainCode/ExecMainStatus for the last exit. Two extra property reads on a path already fetching ActiveState and SubState; failure is non-fatal, since the interface only exists on .service units.

Each tick diffs the counter against a stored per-instance baseline:

  • No baseline — first sighting. Adopt the counter without recording; those restarts happened at times seedling cannot know, and inventing timestamps would corrupt the rate.
  • Counter decreased — a reset (reset-failed, or a recreated unit), not a negative delta. Re-baseline, recording whatever accrued after the reset.
  • Counter increased — record the delta, with the known exit attached to the most recent of the batch.

A start the reconciler issues for an instance it has already run is recorded as deliberate and re-baselines to zero, so a rolling update never reads as a crash burst — and the zeroed counter on the next tick is expected state rather than a reset.

Fault behaviour

crash_loop is filed on the rate reaching the threshold, or on start-limit-hit, and the description names which. Auto-restart suppression is re-derived from the fault table each tick, so it now covers rate-derived loops, survives a daemon restart, and lifts on the next tick when an operator clears the fault.

Retention

Bounded per instance (50 records) on write rather than by rate-limiting recording: a crash loop produces rows fastest exactly when the per-attempt exit codes are the diagnostic. The GC pass drops records and counters for instances that have left the registry.

Operator surfaces

seedling-ctl restarts list|settings|set-settings, and a /restarts web route with an app filter, an instance filter reachable from a per-instance chip on an app's resource table, and the threshold/window editable in minutes.

One incidental fix: useOiQuery kept its effect off the params, so any view that narrowed its query never refetched. It is now keyed on the params' serialisation, which the cache path already computed — object-identity churn from a fresh literal each render still does not refetch.

Note on the migration

v54 is edited in place by the cause rename rather than superseded by a v55. AGENTS.md forbids editing a shipped migration because its schema_version row exists in real databases; v54 is introduced in this unmerged branch and has never been on main, so no such row exists. Anyone who has run this branch locally will need to drop their dev database.

Tests

43 tests covering all five the plan listed: counter deltas, bursts, resets and re-baselining, deliberate-restart exclusion, threshold crossing and the operator setting, the rate trigger filing and observed-healthy clearing the fault, start-limit-hit filing below the rate threshold, per-instance cap under sustained crash-looping, GC of orphans, the observer fact end to end against a stub whose counter moves behind its back, the OI endpoints, and the web route.

cargo clippy --workspace --all-targets is clean, cargo fmt --check passes, tracey validates with 0 errors and 0 stale references, and all 47 frontend test files (319 tests) pass.

Five oi::server tests fail in the dev sandbox with Address family not supported by protocol — they need QUIC/IPv6. Verified as pre-existing by running them at the base commit, where they fail identically; they pass in CI.

🤖 Generated with Claude Code

claude and others added 11 commits August 1, 2026 06:03
Seedling records nothing about individual restarts: crash_loop is filed only
when systemd reports it has given up, so a container that flaps below
StartLimitBurst is completely invisible to an operator.

Plan recording restart attempts in the database and deriving crash_loop from
the recorded rate, with systemd's start limit demoted to a secondary trigger.
Linux first, because it is where this can be tested, and because the Windows
container runtime has no systemd equivalent and will have to own restart
outright — settling the portable shape here means that runtime conforms to a
proven rule rather than one invented alongside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgdfXBAnSEj5s24gbJyXHi
The touch-points table abbreviated the second path in each cell into a
relative continuation, so half the entries were not real paths. Spell them
out. Also note that the wcr[...] rule cited under "what Windows inherits"
lands with #107 and cannot be resolved before then.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgdfXBAnSEj5s24gbJyXHi
…g sink

Seedling writes its own rt.* call records into the journal with the same
SEEDLING_APP/RESOURCE/INSTANCE tags container output carries, so one
field-matched query returns both interleaved in time order. That is the
property that makes `apps logs` useful for debugging — the closure's call
sequence sits against the output it produced — and nothing in any spec said
so. A second runtime could build a correct container-output store, satisfy
every log rule, and silently drop it.

State it, along with the fresh-execution-only rule that keeps a
barrier-suspended operation from flooding the log on every replay pass, and
annotate the two implementation sites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgdfXBAnSEj5s24gbJyXHi
Record every container restart per instance with its exit status and
initiator, derive crash_loop from the recorded rate over an
operator-settable window, and demote systemd's start limit to a
secondary trigger. Adds the restart operator-interface surface and its
retention rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBJbZDHZAfLiRWhZFs2wL
v54 adds instance_restarts (one row per restart, with exit status and
initiator), instance_restart_counters (the supervisor counter baseline
to diff against), and restart_settings (the operator-settable
crash-loop threshold and window).

Records are bounded per instance on write rather than by rate-limiting
recording: a crash loop produces rows fastest exactly when the
per-attempt exit codes are the diagnostic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBJbZDHZAfLiRWhZFs2wL
NRestarts lives on org.freedesktop.systemd1.Service rather than the Unit
interface already proxied, so this adds a second proxy against the same
object path, reading the counter plus ExecMainCode/ExecMainStatus.

A restart that completes inside one observe interval leaves the unit
active at both ends and is invisible to state polling; the counter is
what catches it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBJbZDHZAfLiRWhZFs2wL
Each tick diffs the observed restart counter against the stored
baseline and records what moved, treating a decrease as a counter reset
to re-baseline against rather than a negative delta. Starts the
reconciler issues for an instance it has already run are recorded as
runtime-initiated and excluded from the rate, so a rolling update does
not read as a crash burst.

crash_loop is now filed when the rate reaches the operator-set
threshold, with systemd's start limit as a secondary trigger; the fault
description names which fired. Auto-restart suppression is re-derived
from the fault table each tick, so it covers rate-derived loops and
lifts as soon as an operator clears the fault.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBJbZDHZAfLiRWhZFs2wL
/restarts/list returns records most recent first, filterable by app and
instance; /restarts/settings/{get,set} reads and changes the threshold
and window. app.describe gains a per-instance restart summary, omitted
for instances with no history so a resource that has never restarted
does not read as one that restarted zero times just now.

`seedling-ctl restarts` covers all three.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBJbZDHZAfLiRWhZFs2wL
Lists records most recent first, filterable by app and — when reached
from the restart chip on an app's resource table — by instance, with
runtime-initiated restarts chipped apart from supervisor ones since
only the latter move the rate. The crash-loop threshold and window are
shown and editable, in minutes rather than the wire's seconds.

useOiQuery kept its effect off the params, so a view that narrowed its
query never refetched. Key it on the params' serialisation, which the
cache path already computed, so identity churn from a fresh object
literal each render still does not refetch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBJbZDHZAfLiRWhZFs2wL
The runtime overview said crash-loop detection was derived from the
autonomous-operations log; it is derived from the restart record now, so
that becomes a fourth history alongside the other three.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBJbZDHZAfLiRWhZFs2wL
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBJbZDHZAfLiRWhZFs2wL
@github-code-quality

github-code-quality Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript, Rust

TypeScript / code-coverage/vitest

The overall coverage in commit 840013b in the claude/pr-133-implem... branch is 66%. The coverage in commit 9983b79 in the claude/pr-107-review... branch is 65%.

Show a code coverage summary of the most impacted files.
File claude/pr-107-review... 9983b79 claude/pr-133-implem... 840013b +/-
src/routes/AppDetail.tsx 74% 74% 0%
src/hooks/useOi.ts 97% 97% 0%
src/routes/Restarts.tsx 0% 91% +91%

Rust / code-coverage/rust

The overall coverage in commit 840013b in the claude/pr-133-implem... branch is 58%. The coverage in commit 9983b79 in the claude/pr-107-review... branch is 57%.

Show a code coverage summary of the most impacted files.
File claude/pr-107-review... 9983b79 claude/pr-133-implem... 840013b +/-
crates/core/src/runtime/gc.rs 91% 90% -1%
crates/core/src...handler/apps.rs 70% 69% -1%
crates/core/src...em/reconcile.rs 15% 15% 0%
crates/core/src...stem/systemd.rs 17% 20% +3%
crates/core/src...ncile/faults.rs 0% 4% +4%
crates/core/src/system/stub.rs 25% 43% +18%
crates/core/src...tem/observer.rs 0% 62% +62%
crates/core/src...ile/restarts.rs 0% 75% +75%
crates/core/src...ime/restarts.rs 0% 96% +96%
crates/core/src...ler/restarts.rs 0% 100% +100%

Updated August 01, 2026 08:30 UTC

@passcod passcod left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing this from the Windows runtime side (#107), since the portable rules added here are what that runtime will have to conform to. The implementation follows the plan closely and the spec rules read better than the ones the plan sketched. One thing is worth changing before merge, because it is operator-visible and gets expensive afterwards.

initiator splits on actor where the meaning is cause

i[restart.record] defines initiator as "supervisor" or "runtime", and r[autonomous.restart.rate] counts supervisor-actioned restarts toward the threshold, excluding runtime-initiated ones so rolling updates do not read as crash bursts.

On Linux that is exactly right, and the two meanings line up perfectly: systemd actions crash restarts, seedlingd actions deliberate ones. They line up because of a Linux implementation detail, not because actor and cause are the same thing.

The Windows container runtime has no platform supervisor actioning restarts. containerd has no restart policy and the runhcs shim does not restart tasks, so seedlingd actions every restart there — that is why wcr[shim.ownership] has to drop its restart clause. Under this taxonomy that means:

  • every Windows restart is initiator: "runtime"
  • r[autonomous.restart.rate] counts only supervisor-actioned restarts
  • the rate never reaches the threshold, and the primary crash-loop trigger is dead

The secondary trigger does not cover for it either: r[autonomous.restart.start-limit-hit] keys on systemd's failed/start-limit-hit, which has no Windows equivalent. So the second runtime would end up with no crash-loop detection at all — the precise blind spot this PR exists to close.

The alternative is for Windows to report crash restarts as "supervisor" when no supervisor was involved, which empties the field of meaning.

Suggested change

Split on cause rather than actor: recovery from an unexpected exit, versus a deliberate runtime action. Something like cause: "recovery" | "deliberate" in place of initiator: "supervisor" | "runtime", with the rate counting recovery restarts.

On Linux this is a pure rename — systemd-actioned becomes recovery, deploys and replacements become deliberate, identical records and identical numbers. On Windows it works, because seedlingd is both the actor and the thing that knows why it is restarting something.

Worth noting that r[autonomous.restart.backoff] was already generalised in this PR to "where the platform's supervisor actions restarts", so the PR does anticipate a runtime where it does not. The initiator field simply did not get the same treatment, which reads as an oversight rather than a decision.

Why now

Pre-merge this is a field rename across i[restart.record], r[autonomous.restart.record], r[autonomous.restart.rate], i[restart.settings]'s wording, a DB column, a CLI column and a web column. Post-merge it is a breaking change to a published operator-interface field.

Nothing else blocking from here — the NRestarts baseline handling (adopt on first sighting, re-baseline on decrease, exclude runtime-initiated starts) is the part I would most have expected to get subtly wrong, and it looks right.


Generated by Claude Code

passcod and others added 3 commits August 1, 2026 08:21
Splits the database side of file_crash_loop_faults out the same way the
counter reconciliation was, so the policy can be exercised without
standing up a tick: the rate trigger files and observed-healthy clears,
start-limit-hit files below the rate threshold, a persisting loop is not
filed twice, and clearing is scoped to the instance that recovered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBJbZDHZAfLiRWhZFs2wL
Per repo convention the plan goes at the end of its implementation PR;
the spec rules it called for stay.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBJbZDHZAfLiRWhZFs2wL
initiator (supervisor | runtime) named who performed a restart, which on
Linux happens to coincide with why: systemd actions crash recovery and
seedling actions deploys. The coincidence is a property of this platform,
not of the record. Where there is no service supervisor the runtime
performs both kinds, so every restart would be initiator=runtime, the
rate would count none of them, and the primary crash-loop trigger would
be dead on that runtime — with no start-limit state to fall back on.

cause (recovery | deliberate) says why instead. On Linux it is a pure
rename: identical records, identical numbers. Elsewhere it still means
something.

v54 is edited in place rather than superseded: it is introduced in this
unmerged branch and has never been on main, so no shipped database has a
schema_version row for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBJbZDHZAfLiRWhZFs2wL

passcod commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

Agreed, and done in 840013b. You're right that it's an oversight rather than a decision — autonomous.restart.backoff got generalised and the field didn't, which is exactly the shape of one.

The failure mode you describe is the decisive part: not that the taxonomy reads oddly on Windows, but that it leaves that runtime with no working crash-loop trigger. initiator: "runtime" on everything, a rate that counts nothing, and no failed/start-limit-hit to fall back on — reintroducing precisely the blind spot this exists to close, on the runtime that was the reason for making the requirement portable in the first place.

cause: "recovery" | "deliberate" throughout, with the rate counting recovery. On Linux it is a pure rename: identical records, identical numbers, all 39 tests pass with only the enum swapped.

Both specs now say the split is about why rather than who, so the next reader does not have to rediscover this:

The cause is a statement about why the restart happened, not about which component performed it. Who actions a restart is a platform detail — a platform with a service supervisor leaves recovery to it, and one without leaves the runtime to perform both kinds — so recording the actor would make the record mean different things on different platforms.

Renamed across i[restart.record], r[autonomous.restart.record], r[autonomous.restart.rate], the i[restart.settings] and i[app.describe] wording, w[routes.restarts], the instance_restarts.cause column, Cause in runtime::restarts, the OI field, the CLI help, and the web column and chip.

Two things worth flagging:

The v54 migration is edited in place rather than superseded. AGENTS.md forbids editing a shipped migration because its schema_version row exists in real databases; v54 is introduced in this unmerged branch and has never been on main, so no such row exists and the hash check has nothing to disagree with. Anyone who has run this branch locally will need to drop their dev database. Say the word if you'd rather have a v55 doing the rename.

The plan is deleted in 9263ff6, per the convention that it goes at the end of its implementation PR. That takes the "What Windows inherits" section with it — the wcr[shim.ownership] restart clause, folding the exit observation into history before containerd reaps the task, and stating the daemon-down gap as a property. It survives in #133's description and in git history, but nothing in-tree carries it now. Happy to reproduce it as a comment on #107 if that's a more useful home.

I also filled in the two tests the plan listed that I'd missed: the rate trigger filing and observed-healthy clearing the fault, and start-limit-hit filing below the rate threshold. Both needed the same DB-side extraction I'd already done for the counter reconciliation.


Generated by Claude Code

@passcod passcod mentioned this pull request Aug 1, 2026
passcod pushed a commit that referenced this pull request Aug 1, 2026
wcr[shim.ownership] claimed the runhcs shim restarts the workload per policy.
It does not — containerd has no restart policy and restart is a client
concern — and with restart accounting landed, the portable spec now places
crash-loop detection in the reconciler outright. Drop the clause and say where
restart actually lives.

Three Windows-specific consequences come with it. Restarts are recorded at the
point the reconciler actions them, so the counter inference the Linux runtime
needs is not needed here — and the recorded cause is recovery versus
deliberate, not who acted, because seedlingd performs both kinds and an
actor-shaped field would leave the crash-loop rate counting nothing. A
terminated container's exit status must be read before its record is reaped,
or the record is a tally with nothing to diagnose from. And a workload that
exits while seedlingd is down stays down until it returns, which is a stated
property of owning restart in the control plane rather than something to be
found later.

r[autonomous.restart.record] and r[autonomous.restart.rate] land with #134;
the references resolve once both are on main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgdfXBAnSEj5s24gbJyXHi
@passcod
passcod changed the base branch from claude/pr-107-review-vq6bt3 to main August 1, 2026 22:07
@passcod
passcod marked this pull request as ready for review August 1, 2026 22:07
Copilot AI review requested due to automatic review settings August 1, 2026 22:07
@passcod
passcod added this pull request to the merge queue Aug 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds durable restart accounting to the Linux runtime (observing systemd’s NRestarts/last exit), derives crash_loop from a configurable restart-rate threshold/window, and exposes the resulting history + settings through OI, CLI, and the web UI.

Changes:

  • Persist restart records and per-instance baselines in the DB, compute rate-derived crash loops, and GC retained restart history.
  • Extend reconciliation to observe systemd restart counters and file crash_loop faults from either restart-rate or start-limit-hit triggers (with suppression derived from the fault table each tick).
  • Add operator surfaces: /restarts/* OI endpoints, seedling-ctl restarts …, and a /restarts web route plus per-instance restart summary in app.describe.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
docs/spec/web.md Specifies the new /restarts web route requirements.
docs/spec/runtime.md Defines restart recording, rate-based crash-loop detection, settings, and GC expectations.
docs/spec/interface.md Defines restart record/list/settings interfaces and adds restarts summary to app.describe.
docs/runtime-overview.md Updates conceptual model to include “Restart History” as a fourth persistent history.
crates/web/frontend/src/routes/Restarts.tsx Implements the /restarts UI: list + filters + editable rate settings.
crates/web/frontend/src/routes/Restarts.test.tsx Frontend tests for the restarts route behaviour and settings mutation payload.
crates/web/frontend/src/routes/AppDetail.tsx Adds per-instance restart summary chip linking to /restarts?instance=….
crates/web/frontend/src/routes/AppDetail.resources.test.tsx Tests restart chip rendering/linking behaviour in AppDetail resources table.
crates/web/frontend/src/lib/types.ts Adds frontend types for restart records/settings/summary.
crates/web/frontend/src/hooks/useOi.ts Fixes useOiQuery to refetch when params change (value-based keying).
crates/web/frontend/src/components/Navbar.tsx Adds navbar link/icon to /restarts.
crates/web/frontend/src/App.tsx Registers the new restarts route.
crates/ctl/src/restarts.rs Adds `seedling-ctl restarts list
crates/ctl/src/main.rs Wires the new restarts subcommand into the CLI.
crates/core/src/system/types.rs Extends unit state and observation facts to carry restart counter + last exit.
crates/core/src/system/systemd.rs Adds systemd Service proxy reads for NRestarts/ExecMain* and parsing.
crates/core/src/system/stub.rs Extends stub process manager to simulate restarts/counter resets and last exits.
crates/core/src/system/reconcile/restarts/tests.rs Unit tests for counter delta logic, resets, deliberate exclusion, threshold, and GC.
crates/core/src/system/reconcile/restarts.rs Implements baseline reconciliation, recording, rate evaluation, and restart GC.
crates/core/src/system/reconcile/pods.rs Threads restart counters through pods phase and adds crash-loop cause modelling.
crates/core/src/system/reconcile/phases.rs Plumbs crash_looped suppression set into pods phase execution.
crates/core/src/system/reconcile/faults/crash_loop_tests.rs Tests crash-loop fault filing/clearing for both triggers and deduping.
crates/core/src/system/reconcile/faults.rs Updates crash-loop fault handling to support multiple triggers and clearer descriptions.
crates/core/src/system/reconcile.rs Adds per-tick derivation of crash_looped from the fault table and runs bookkeeping.
crates/core/src/system/observer.rs Emits UnitRestartCounter observation facts for deployment/job units.
crates/core/src/system/breadcrumb.rs Adds tracey annotation for breadcrumb emission requirement.
crates/core/src/runtime/restarts/tests.rs Tests DB restart recording, filtering, rate-window logic, caps, summaries, and settings validation.
crates/core/src/runtime/restarts.rs Implements DB schema operations for restart records, summaries, baselines, and settings.
crates/core/src/runtime/gc.rs Hooks restart-record GC into the periodic GC cycle.
crates/core/src/runtime/db/tests.rs Bumps expected schema version to 54.
crates/core/src/runtime/db/migrations/v54.sql Adds tables for restart records, baselines, and restart-rate settings.
crates/core/src/runtime/db.rs Registers migration v54.
crates/core/src/runtime/barrier/replay.rs Adds tracey annotation for breadcrumb replay-boundary behaviour.
crates/core/src/runtime.rs Exposes the new runtime::restarts module.
crates/core/src/oi/handler/restarts/tests.rs Tests OI restart list/settings endpoints end-to-end.
crates/core/src/oi/handler/restarts.rs Adds OI handlers for restart list and settings get/set.
crates/core/src/oi/handler/apps.rs Adds per-instance restart summary into app.describe responses.
crates/core/src/oi/handler.rs Registers /restarts/* routes in the OI dispatcher.
Suppressed comments (1)

crates/core/src/oi/handler/restarts.rs:61

  • As above in list_restarts, get_settings maps DB failures to ErrorCode::NotFound. This should be ErrorCode::Internal so clients can distinguish missing resources from server/database failures.
        .map_err(|e| OiError::new(ErrorCode::NotFound, format!("db query: {e}")))?;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 681 to 685
"transition_time": transition_time.and_then(|t| {
jiff::Timestamp::try_from(t).ok().map(|ts| ts.to_string())
}),
"restarts": restart_summary,
})
Comment on lines +1111 to +1116
pods::CrashLoopCause::RestartRate { count, window_secs } => format!(
"{} restarted {count} times in the last {} minutes. \
Auto-recovery is paused until this fault is cleared.",
instance.display_name,
window_secs / 60,
),
Comment thread docs/spec/web.md
> The navbar's held-volumes badge must reflect the current count of held volumes without requiring a page reload, both when new held volumes are created and when the operator confirms their deletion.

> w[routes.restarts]
> The web interface must expose container restart history at `/restarts`, listing [restart records](interface.md#i--restart.record) most recent first with their app, instance, time, initiator, and exit status.
Comment on lines +419 to +424
let _ = db.conn.execute(
"UPDATE restart_settings
SET threshold = ?1, window_secs = ?2, updated_at = ?3
WHERE singleton = 1",
rusqlite::params![next.threshold, next.window_secs, now_ms()],
);
let records = state
.db
.call(move |db| restarts::list(db, app.as_ref(), instance.as_deref(), limit))
.map_err(|e| OiError::new(ErrorCode::NotFound, format!("db query: {e}")))?;
Merged via the queue into main with commit 2783d12 Aug 1, 2026
14 checks passed
@passcod
passcod deleted the claude/pr-133-implementation-yoa1va branch August 1, 2026 22:15
passcod pushed a commit that referenced this pull request Aug 1, 2026
Brings in restart accounting (#134), so wcr[restart.ownership] and
wcr[restart.record]'s references to r[autonomous.restart.record] and
r[autonomous.restart.rate] resolve within the branch, and the fault-cleared
test fix (#135).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants