Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Proposal: centralize daemon retirement and session lifetime ownership

Open
#3,116 4 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Refactor
Clarity
Mostly clear
Activity status
Active
Tech stack
node.js, typescript

Research direction

Start by rechecking the cited source inventories against main at c237027737, then read the existing registration module and the proposed src/daemon-registration.ts boundary. Resolve the host-kit lock algorithm and mixed-version policy before implementing the daemon retirement and session-lifetime APIs. Done requires the settled APIs, ownership checks, cleanup sequences, and regression tests described in the issue.

Written by the indexing model from the issue text.

Description

needs-triage refactor

Put identity checks, sequencing and cleanup inside the functions that own the work. Callers should be able to retire a daemon or finish a session resource with one operation, without repeating the safety rules.

Priority: registration and the daemon lock first, session lifetimes second. The payoff is correctness and fewer caller obligations. Changing classes to factories is optional.

Status: needs triage. Two design gates remain before registration integration:

  1. Specify and prove the hardened host-kit lock algorithm.
  2. Choose mixed-version exclusion or an unsupported-concurrency policy that the cutover can enforce.

The other decisions below are settled; their APIs and tests still need implementation.

Baseline: main at c237027737, verified 2026-10-02. The cited source files are unchanged from e4716e6f51. Recheck the inventories against each implementation base.

Make each function own the whole operation

The repeated problem is an address passed separately from the resource: registration paths plus a process identity, or a session name plus a record. Bind them at the owning operation.

Caller Operation What the operation owns
Client lifecycle stopAndRetireDaemon / recoverAbandonedDaemonRegistration Process proof, stop policy, lock acquisition, metadata cleanup and result
Daemon lifecycle Registration functions bound to its acquired lock Publication, shutdown reports, own metadata retirement and release
Routes and session lifecycle Recording, resource and retirement functions taking SessionRef Lifetime check, latest record, field transition and artifact address
Device-claim recovery Artifact path functions Validation and path layout, without constructing a session store
flowchart TD
    C["Client lifecycle"] --> R["Shared registration jobs"]
    D["Daemon lifecycle"] --> R
    R --> H["host-kit process identity and lock"]
    Q["Routes and session lifecycle"] --> J["Recording and resource jobs"]
    J --> S["One session lifetime owner"]
    J --> A["Artifact paths and journal operations"]
    O["Device-claim recovery"] --> A

Use ordinary functions and composition. Keep private state where ownership needs it, in a small class or factory. Keep adapters that translate records or restrict authority; replacing one broad store with several broad objects does not reduce caller obligations.

Retire a daemon safely

Deepen the existing registration module into a shared root module, such as src/daemon-registration.ts. It already distinguishes matching, replaced, unproven, absent, unreadable and ownerless records. Client runtime imports from daemon internals are forbidden, so this implementation must sit above both.

Move takeover, replay shutdown, failed-startup cleanup and timeout reset to the shared operations. Timeout reset starts a fallback stop without awaiting it, then removes metadata. Awaiting the existing stop function is also insufficient: it can decline signaling or exhaust its exit wait.

Return proof and cleanup outcomes

The proposed boundary is:

type DaemonRetirementInput = Readonly<{
  paths: DaemonPaths;
  observed: DaemonRegistrationObservation;
  mode: 'graceful' | 'force';
  ownedStateDir?: OwnedReplayStateDir;
}>;

declare function stopAndRetireDaemon(
  input: DaemonRetirementInput,
): Promise<DaemonRetirementResult>;

declare function recoverAbandonedDaemonRegistration(
  input: Omit<DaemonRetirementInput, 'mode'>,
): Promise<DaemonRetirementResult>;

type ConfirmedDaemonTermination = Readonly<{
  owner: DaemonProcessIdentity;
  kind: 'exited' | 'already-dead';
}>;

type DaemonRetirementResult =
  | {
      status: 'retired';
      termination: ConfirmedDaemonTermination;
      removedInfo: boolean;
      removedStateDir: boolean;
    }
  | { status: 'absent' }
  | {
      status: 'retained';
      termination?: ConfirmedDaemonTermination;
      reason:
        | 'ownership-unproven'
        | 'registration-replaced'
        | 'stop-failed'
        | 'exit-unconfirmed'
        | 'lock-busy'
        | 'metadata-unreadable'
        | 'retirement-unconfirmed';
    };

Finalize the observation, private-directory capability, partial-failure details and error mapping before implementation. absent means metadata is absent under the lock; it does not prove process exit or authorize directory removal.

The function owns signaling and configured TERM/KILL wait budgets:

Caller Mode Policy
Timeout reset force Verify identity, send KILL immediately, await exit
Takeover and ordinary shutdown cleanup graceful Verify identity, send TERM, wait, escalate to KILL for the same lifetime, await exit
Abandoned-registration recovery No stop mode Prove abandonment; never signal a live process

No caller pre-signals or starts a fire-and-forget fallback.

Use one retirement sequence
  1. Confirm exit. Validate the observed process lifetime before signaling. Proven death skips signaling. Missing process start time, unreadable liveness, failed signaling without independent exit proof, or exhausted waits retain metadata and the owned directory.
  2. Acquire the startup lock. Use a bounded wait; host-kit alone reclaims proven dead claims. Lock release by a still-running daemon is insufficient.
  3. Re-read and retire under the lock. Remove daemon.json only if it still names the confirmed dead identity. Retain replaced, unreadable or unproven metadata.
  4. Release this acquisition. Report success only when the postconditions are confirmed. Keep typed partial outcomes for metadata or release failures.

Abandoned-registration recovery uses the same protected core. It never rediscovers and stops a live winner. Even an absent result requires acquisition and inspection under the lock.

sequenceDiagram
    participant C as Client retirement job
    participant P as Observed daemon lifetime
    participant L as Shared registration lock
    participant M as Daemon metadata
    C->>P: Verified stop and awaited exit
    P-->>C: Confirmed termination
    C->>L: Acquire; host-kit recovers dead claim
    L-->>C: Held acquisition
    C->>M: Read current registration
    alt Record names the dead identity
        C->>M: Remove while lock is held
    else Replaced or unproven
        Note over C,M: Retain current metadata
    end
    C->>L: Release this acquisition

Process identity permits signaling and matching a record. Lock possession prevents concurrent mutation. Both are required; checking identity and then unlinking without the lock leaves a race.

Bind daemon writes to its acquired lock

Daemon-side registration functions close over the actual acquisition. They assert it is still held immediately before each metadata/report mutation. Never reconstruct release authority from a path or PID.

Preserve these sequences:

Report clearing happens before daemon.json exists, so metadata ownership cannot authorize it. A released or lost acquisition cannot write or clear a report. Keep report readers, contents and best-effort reporting, and preserve primary errors plus normalized hint, diagnosticId, logPath and typed details.

Retire only the private replay directory you own

The directory capability comes from private mkdtemp creation and binds to its startup/daemon attempt. Before deletion:

  1. Confirm that daemon exited and all owned startup attempts completed.
  2. Acquire the registration lock inside the directory.
  3. Inspect repair evidence and retain held repair sessions, active close-less replay sessions and unrecovered repair-commit-failure evidence.
  4. Remove the eligible directory while holding the lock, then release.

Use the same lock for exclusion. This authority does not permit recursive deletion of a shared state directory. The immediate defect is deleting a private replay directory beneath its still-running daemon; normal successor occupancy has not been established.

Host-kit release already handles a missing owner file as not-owner without recreating the path. Add a regression test after guarded directory removal; no new release behavior is needed.

Relaunch startup after a transient holder releases

Failed-startup cleanup stays bound to its own attempt and cannot stop or remove a winning contender. The existing startup wait handles another daemon as holder; the replacement must also handle a client holding the lock during retirement:

  1. A child that loses nonblocking acquisition reports a dedicated lock-busy disposition through a shared named exit code and monitored launch result. Generic early exits, clean exits and stderr text are not contention proof.
  2. Join the losing child. Wait while inspection says held/publishing, regardless of the holder's role. Unknown inspection retains state and reports diagnostics.
  3. Attach if a winner publishes a usable registration, preserving version, code-signature and policy admission.
  4. If the claim is released or proven dead without a usable registration, launch a fresh attempt. Only acquisition reclaims dead claims.
  5. Keep the same startup deadline and existing bounded attempt budget across relaunches. Join every loser; exhaustion returns a typed failure and leaves the winner untouched.

Do not add daemon/retirement roles to host-kit's generic owner record.

After migration, delete raw info/lock removal exports, cleanupStaleDaemonLockIfSafe and recoverDaemonLockHolder. Every metadata/report mutation belongs to a held registration owner; every lock reclaim belongs to host-kit acquisition.

Replace the daemon lock

The bespoke daemon lock permits both contenders to succeed:

  • A second contender unlinks the first contender's incomplete JSON publication.
  • Two contenders read an abandoned claim; the second unlinks the first's replacement claim.

These are demonstrated unsafe interleavings, not frequency measurements. Use one host-kit process lock for the daemon's whole lifetime, with acquisition identity for reclaim/release.

The host-kit primitive needs:

Requirement Contract
Safe publication and reclaim A pause beyond publication grace cannot let an old claimant overwrite a successor. A reclaim mutex cannot be stolen solely because its live holder paused. Specify and prove the algorithm.
Nonblocking startup acquisition Make one real attempt with typed acquired/busy/unproven outcomes and no polling or sleep. timeoutMs: 0 makes zero attempts in the existing loop. Bounded client acquisition uses the same protected reclaim mechanism.
Read-only holder inspection Report held, publishing, absent and unreadable/unknown claims, with available liveness facts. A claim may precede metadata. Keep parsing/layout private; inspection does not authorize unlinking.
Lock ownership assertion Supply an acquisition-bound assertion for registration/report mutations alongside the release function.
Unknown-liveness handling Retain the claim and emit a typed reason plus an operator hint: restore inspection or stop a verified owner, then retry. Manual recovery requires external confirmation that all users of the state directory stopped. No automatic age-only or forced-delete fallback.

isAgentDeviceDaemonProcess(...) === false also covers missing start time, missing command and unreadable identity. Reclaiming on that value is unsafe. Blocking uncertain holders with diagnostics is a deliberate behavior change.

Remove the duplicate server lock type and client lock parser at cutover. Lock version has no reader and startedAt is parsed but unused; do not copy them into host-kit. Preserve daemon.json version/code-signature negotiation.

Cutover remains open. Checking that the legacy path is absent before acquiring a different path cannot exclude an older daemon starting afterward. Choose actual mixed-version exclusion or enforceable refusal under an explicit unsupported-concurrency policy. Test old daemons already running and starting concurrently. Keep legacy files out of host-kit's stray reclamation. Use one steady-state lock owner and base the legacy sunset on the support policy, not a release count. Verify tag/release history before claiming compatibility obligations.

Host-kit hardening and registration design can proceed in parallel. Integration waits for the proven primitive, inspection/assertion APIs and cutover.

Keep session work tied to one lifetime

SessionRef captures a stored address and a mutable record. Lookup/list/find return fresh wrappers; rebuilding a record does not retarget an older wrapper.

Keep three concepts distinct:

Concept Meaning
Address The store key, such as cwd:<hash>:default; use it for paths, journals and store operations
Lifetime One occupant of that address, from publication to retirement
Record That lifetime's session state, which can be rebuilt

Repeated successful open rebuilds the same lifetime. Keep its resources, recording handle, actions, creation time, claims and idle-expiry handling. Preserve record construction and the second-open script-authoring abort rule, which differs from screen recording. Delete followed by publication at the same address creates a new lifetime. No inventoried caller needs a separate occupied-address/new-lifetime replace operation.

Store one stable entry per lifetime

Replace the existing map with stable entries. Each ref carries the entry's opaque identity and its captured record; .session stays a captured record, not a live getter.

type SessionRef = Readonly<{
  address: string;
  session: SessionState;
  lifetime: object;
}>;

type SessionEntry = { current: SessionState };
const entries = new Map<string, SessionEntry>();

function lookup(address: string): SessionRef | undefined {
  const entry = entries.get(address);
  return entry
    ? { address, session: entry.current, lifetime: entry }
    : undefined;
}

function resolveCurrent(ref: SessionRef): SessionState | undefined {
  const entry = entries.get(ref.address);
  return entry === ref.lifetime ? entry.current : undefined;
}

Keep entry contents and construction private. This is the canonical map, with no second registry or generated generations.

The store owns three operations:

  • Publish: refuse an occupied address and honor daemon shutdown/admission.
  • Update: require the captured lifetime; derive from its latest record; never insert a missing entry.
  • Retire: check that lifetime before removing the record and its runtime hints.
stateDiagram-v2
    [*] --> Draft
    Draft --> Live: publish at the existing adoption point
    Draft --> [*]: preparation or adoption fails
    Live --> Live: rebuild record or reopen app
    Live --> Retired: guarded deletion
    Retired --> [*]
    note right of Retired
        Address reuse creates a new lifetime.
        Old work gains no authority over it.
    end note
Put lifetime checks inside session operations

Recording, resource finish and teardown accept one ref. They resolve the latest matching record, run the owning field transition and use ref.address internally.

  • Recording owns recording transitions and journal append.
  • Repair finalization owns synthetic-close recording, script publication, failure classification and tombstones.
  • Resource functions bind their capture read/write/path adapters internally.
  • Retirement keeps hint cleanup, repair outcomes and idle markers tied to the captured lifetime.

Old work cannot gain authority by looking up its address again. Audit refs held by request binding, lock policy, replay, inventory and asynchronous cleanup. Read projections may intentionally use the captured record; durable writers resolve the matching current record inside their owning functions.

Cleaning up an old captured resource can continue after retirement. Guard the store mutation separately: clearing a slot requires both the lifetime and matching resource identity. Preserve durable resource fences. If required capture adoption loses its lifetime, run existing failed-adoption disposal/recovery; do not report success or overwrite successor evidence.

Keep publication points unchanged:

Session path Publication point
Record-only capture After successful adoption
Sessionless snapshot After capture
Provisional open Existing before-dispatch point

Shutdown can overlap canceled handlers and timed-out cleanup. Updates cannot resurrect a retired entry. An unpublished draft also needs the existing shutdown/admission owner's check before first publication; entry identity alone cannot prevent that late publication.

Preserve execution locks, idle-reaper lock order, shutdown order, wire errors, hints and artifact naming. Keep close and teardown sequencing separate: platform close, browser cleanup and materialized paths differ.

Keep field ownership enforceable

Use one lifetime-checked update for published-record replacement. Field owners pass explicit patches; the store merges them into the latest matching record:

updateSession(ref, { appLogFailure: undefined });

Keep this operation private to the owner/adapters. Domain callers use functions such as clearSessionAppLogFailure(ref). Holding a ref does not authorize every field. Existing owner-controlled in-place transitions remain in their domain functions and resolve the matching latest record.

The R7 scanner checks assignments but misses whole-record spread overrides. appLogFailure is declared store-established, yet the app-log owner writes it through spreads. Declare app-log-session-resource.ts as its owner and keep resource-slot transitions in their field owner.

Extend R7 with two syntax rules:

  1. Check top-level update keys against SESSION_STATE_FIELD_OWNERS. Accept an explicit patch literal or inline synchronous callback returning explicit named keys. Reject computed keys, patch spreads and opaque patch variables.
  2. Allow whole-SessionState spreads from an existing record only inside session-store.ts and declared draft constructors for open, sessionless snapshot and record-only capture. Existing-session branches use patches. Nested value spreads, such as a lease, remain allowed.

A callback reads the latest record when derivation needs it. Keep it synchronous and non-reentrant: one ownership check is enough. Do not add asynchronous callbacks or general value tracing.

Migrate sessionSlot.replace whole-session copies to explicit slot patches through a ref-bound capability. Capture-kit supplies the resource transition; the daemon adapter supplies its named field update. Do not bypass the update with a constructed whole record.

R7 fixtures must reject the existing app-log spread, unauthorized failure patches, computed keys, spread patches and opaque patches; pass the declared owner's explicit patch; and retain direct-assignment checks.

Migrate the callers together

There are 19 production session-record set sites: 16 in the daemon and three in capture-kit. Seven re-set the same object, eight rebuild it, and four mix creation with rebuilding. There are six direct delete callers. Exclude tests, runtime hints and internal Map operations.

Set inventory: all 19 sites and their migration
Site(s) Migration
interaction/index.ts:67 Remove same-object re-set after migrating snapshot mutation and reverse-lookup fallback
interaction-runtime.ts:71 Migrate owner write; remove re-set
selector-capture-runtime.ts:335 Migrate owner write; remove re-set
selector-runtime-backend.ts:84 Migrate owner write; remove re-set
selector-runtime.ts:94 Migrate ref-frame write; remove re-set
request-execution-scope.ts:493 Migrate recording-health write; remove re-set
session-replay-command.ts:115 Migrate coordinator write; remove re-set
app-log-session-resource.ts:120, 138 Same-lifetime patches for failure set/clear
session-app-deployment.ts:91 Same-lifetime Harmony app identity patch
session-selector-dispatch.ts:99 Same-lifetime patch from command result
lease-lifecycle.ts:129 Same-lifetime lease refresh
session-perf-runtime.ts:271 Same-lifetime profile patch; remove address-only fallback
capture transitions.ts:193, 232 Clear only the matching lifetime/resource slot
session-open-execution.ts:329, 356 Separate fresh publication from existing/provisional updates in each branch
snapshot-command-runtime.ts:147 Publish sessionless capture after capture; otherwise update captured lifetime
capture adoption.ts:45 Publish record-only draft; otherwise update after successful adoption
Delete inventory: all six callers and the policies to preserve
Site Retirement policy
record-runtime.ts:255 Retire record-only lifetime after successful recording stop
record-runtime.ts:264 On failed stop, retire only if its manifest is terminal
lease-lifecycle.ts:103 Teardown and claim cleanup, then retire captured address/lifetime
daemon-runtime.ts:205 Bounded teardown finalizes repair and retires even on cleanup timeout; guard later writes
daemon-session-idle-expiry.ts:494 Settle resources/claim before repair, retirement and idle marker; preserve failed-settle retention
session-close.ts:343 Preserve platform/resource teardown, provider/lease/claim handling and error reporting before guarded record/hint removal

The two Map.delete operations inside SessionStore.delete remove hints and the record; they are not extra callers.

Pair all seven re-set removals with their owner-write migration. A stale re-set loses an intervening rebuild. Removing it alone loses the later mutation on a detached object instead. Route the write through the latest matching record, remove the re-set, and prove both updates survive. Keep all seven in the session migration.

The boundary changes are:

  • Capture-kit: DurableCaptureSessionStore is only a narrowed type over the full store. Replace that dependency with a ref-bound read/write/path adapter that reports retired/resource-changed outcomes. Give drafts a separately admitted publication capability at the existing adoption point.
  • Command runtime: bind createDaemonRuntimeSessionStore getters and its three write projections to the captured lifetime. Screenshot's no-op setter is a read projection, not a twentieth set site.
  • Late updates: remove get(address) ?? oldRecord from perf and teardown:34,211,226. It can reinsert a deleted record or adopt a successor. App-log patches must also preserve intervening resource updates.
  • Lease expiry: teardown and deletion use session.name instead of the lookup's stored params.sessionName. Use ref.address for paths, slots and deletion; public name stays for display. Test a scoped leased address plus a separate slot under its public name. This is a helper-contract defect; production reachability with unequal names remains unproved.

Remove unrestricted upsert and resolveStoredSessionName after migration. Its object-identity scan and public-name fallback must not survive as compatibility. A rebuilt cwd-scoped default must use cwd:<hash>:default for journals, without creating a phantom public-name record.

Reduce the store interface in this change. Convert it to a factory only if that helps the resulting composition. Keep runtime projection, ref-frame authorization and capture merging adapters that still own useful behavior.

Land independent cleanups

These do not gate registration or include the seven re-set removals:

  1. Move canonical session-directory/app-log path functions into src/daemon/session-artifact-paths.ts. Device-claim recovery constructs a store only for these paths; remove that dependency while preserving validation, stored addresses, layout and errors.
  2. Replace SessionStore.expandHome with host-kit's expandSessionPath and remove the forwarder.
  3. Optionally replace SessionScriptWriter with writeSessionScript(sessionsDir, session, options). It stores one directory and its only production user is SessionStore. Keep formatting, no-clobber/force behavior, commit transitions, diagnostics, failure routing and public-name-based default filenames together.

Prove the behavior

Use deterministic interleavings and real temporary filesystem/process tests for exclusion and exit guarantees. A Map-only test cannot prove filesystem exclusion.

Registration regression checklist
Scenario Required result
Partial publication or two stale-lock reclaimers At most one owner succeeds; loser cannot remove winner
Claimant/reclaimer pauses beyond grace Cannot overwrite/release successor or lose reclaim mutex merely through age
Free, held and unproven nonblocking acquisition One real attempt; no polling/sleep; typed outcome
Old daemon running or starting concurrently Enforced cutover policy; no split exclusion or legacy stray reclamation
Replaced/unreadable metadata or unproved process start time Retain/refuse; no unproved-owner signaling or destructive cleanup
Unknown lock identity/liveness Retain state; typed reason and operator hint; no age-only fallback
Force reset versus graceful takeover Operation owns verified KILL-first versus TERM-then-KILL and awaits exit; callers do not pre-signal
Signal failure or exhausted exit wait Keep metadata/private directory unless independent death is proved
Losing startup attempt Cleanup cannot stop/remove winner
Startup meets a client-held retirement lock Typed busy; join loser; wait regardless of role; attach or relaunch with original deadline/attempt bounds
Non-contention early exit Existing failure policy; no false busy classification or winner cleanup
Winner publishes after old daemon exits Retirement re-reads under the lock and retains winner metadata
Startup clears report before metadata publication Held acquisition authorizes clearing
Released/lost acquisition mutates report Refused; successor report remains intact
Guarded private replay-directory deletion Confirm exit and completion of owned startups; retain repair evidence; release does not recreate path
Session regression checklist
Scenario Required result
Rebuild while a resource operation awaits Latest matching record; preserve unrelated app-log/profile changes
Former same-object re-set after rebuild Both rebuild and later owner mutation survive
Delete followed by address reuse Old work cannot adopt, reinsert, delete successor or clear its hints/slots
Old captured resource needs cleanup Dispose its handle without changing successor/durable evidence
Record-only capture fails or loses adoption admission No premature publication; disposal/recovery evidence retained
Repeated open with active recording Same lifetime/handle; authoring second-open rule preserved
Scoped leased address differs from public name Addressed lifetime retires; public-name slot untouched
Rebuilt cwd-scoped session is recorded Correct journal/address; no phantom public-name record
Shutdown overlaps canceled handler continuation No resurrection or successor adoption; unpublished draft cannot publish after admission closes
Bounded cleanup times out, then finishes Captured handle can finish; late capture/perf/app-log writes cannot reinsert or clear another lifetime
Snapshot/find/ref-frame/idle expiry Issuance, locks, errors and cleanup order preserved
Forbidden R7 writer/patch syntax Unauthorized assignments/keys and forbidden spreads/opaque/computed patches fail; owner's explicit patch passes

Prove reachable schedules separately from interface hazards. Request locks serialize ordinary open/close. Shutdown is an exception: session teardown takes no execution locks, server close forces closure after five seconds, and socket closure cancels without joining handlers. Exercise that real transport path and a precise handler continuation; existing open cancellation checks can block a particular interleaving.

Regression tests must fail when the relevant ownership check is removed or cleanup is made unconditional. Plant the concrete forbidden app-log spread for R7. Colocate tests with source and retain existing tests for pure moves.

Each slice runs focused checks and pnpm check:affected --run on its exact head, plus the full deterministic gate for a broad refactor as repository guidance requires. Preserve CI-owned provider/native/device checks. Report production additions/deletions separately from tests and moves, and name the mechanism or caller obligation removed.

Deliver in dependency order

  • Prove host-kit acquisition/reclaim; add nonblocking acquisition, inspection and lock ownership assertion. Settle mixed-version cutover.
  • Finalize retirement inputs/results, stop modes, bounded relaunch and operator diagnostics; migrate registration/report operations. Remove raw cleanup/recovery exports and legacy lock types/parsers.
  • Land independent path/forwarder cleanups; optionally simplify the script writer.
  • Implement stable entries/captured refs, audit ref holders, and enforce explicit update keys/whole-session spread rules.
  • Migrate all 19 set sites, six delete callers, recording/disposal inputs and runtime/capture adapters. Pair the seven re-set removals with owner writes; preserve adoption timing and resource fences. Remove upsert/reverse lookup and shrink the store interface.
  • Complete the regression checklists and record exact-head validation, including real shutdown/cancellation and late cleanup.

Related work: #3102 already adds daemon metadata publication/removal fencing; deepen it. Coordinate timeout reset with #3105 and report mutations with #3104. Preserve the #2833 idle-reaper lock seam/order. This follows #3069 without reopening it or closing related issues by association.

Host-kit hardening and registration design can proceed in parallel; integration waits for the two design gates. Session migration follows the lifetime contract. Split issues/PRs must name exact prerequisites and branch bases, rather than depend on an uncommitted sibling fix.

Out of scope: a generic request executor, selector-observation aggregation, global immutable-state migration, actors/manager frameworks and promised line-count/performance gains. Request finalization and diagnostics may be separate bounded follow-ups; they do not gate this work.

Dominant language
TypeScript
Stars
4.8k
Forks
315
Avg merge
11h 17m
Merged PRs (30d)
536

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from callstack/agent-device

All issues in callstack/agent-device

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.