Bump docfx from 2.78.3 to 2.78.4#3
Closed
dependabot[bot] wants to merge 1 commit into
Closed
Conversation
dependabot
Bot
force-pushed
the
dependabot/nuget/dot-config/docfx-2.78.4
branch
2 times, most recently
from
February 23, 2026 19:26
f6eb381 to
00b960a
Compare
--- updated-dependencies: - dependency-name: docfx dependency-version: 2.78.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]>
dependabot
Bot
force-pushed
the
dependabot/nuget/dot-config/docfx-2.78.4
branch
from
February 24, 2026 00:13
00b960a to
365e0a5
Compare
Contributor
Author
|
Superseded by #18. |
This was referenced Apr 12, 2026
This was referenced May 13, 2026
Aaronontheweb
referenced
this pull request
in Aaronontheweb/netclaw
Jun 3, 2026
… check - doctor: inject DaemonConfig for the channel instead of hand-reading netclaw.json, so a non-string Daemon.UpdateChannel no longer crashes the whole `netclaw doctor` run (findings #1, netclaw-dev#4). - update-check: drop the low-value 1h TTL and keep a plain last-result store for the daemon /status API (finding #3). Make `channel` required (no default) on EvaluateManifest + CheckForUpdateAsync so a forgotten arg can't silently fall back to stable (finding netclaw-dev#9). - SemVer: parse numeric prerelease identifiers as long, matching the bash generator's unbounded ints (finding netclaw-dev#6). - /status: report FullVersion in the no-check-yet branch for consistency with the post-check branch (finding netclaw-dev#8). - dotted prerelease convention (beta.N): update docs/examples + widen the property-test generators + add example cases; the release version gate now rejects mixed identifiers like `beta1` so a non-dotted tag can't ship and silently mis-order the channel (finding #2). - conformance: extract the generator's precedence key to feeds/scripts/semver_key.py and assert BOTH it and the C# SemVer comparator order one shared fixture (feeds/scripts/semver-order.txt) — via a C# test and a CI check — so the two implementations can't drift (finding netclaw-dev#7). Also updates the release-channels OpenSpec change (dotted-tag requirement plus risk/decision notes).
Aaronontheweb
added a commit
that referenced
this pull request
Jun 3, 2026
* feat(update): channel-aware, semver-correct update check (#1027) Make the binary update check honor an opt-in beta channel and compare versions by SemVer 2.0.0 precedence, so beta testers are notified of the next prerelease while stable users are never offered one. - BinaryFeedManifest: add `latestPrerelease` (newest of {stable, prerelease}). - SemVer: self-contained 2.0.0 precedence comparator (no NuGet.Versioning), matching the bash manifest generator's rules; IsNewerVersion uses it instead of System.Version (which couldn't parse a prerelease suffix at all). - BuildInfo.FullVersion: read the assembly informational version (keeps `-beta1`) so a beta build doesn't report its stripped core and strand. - DaemonConfig.UpdateChannel (stable default | beta) + config schema; parse fails loudly on an unknown value. - EvaluateManifest/CheckForUpdateAsync are channel-aware: stable reads only `latest`; beta reads `latestPrerelease` and rolls onto a superseding stable. - Thread channel + FullVersion through the daemon check, `netclaw update`, the startup notice, `netclaw status`, and `netclaw doctor`. Stable clients structurally never read `latestPrerelease`. The check stays advisory-only; Daemon.DisableSelfUpdate still blocks in-place update. Tests: SemVer precedence, channel-aware evaluation, ParseUpdateChannel. * docs(openspec): add release-channels capability spec Capture the beta (prerelease) release-channel capability end-to-end: manifest pointer semantics, prerelease-aware publishing, installer/Docker channel selection, and the channel-aware update-check policy. Documents both PR #1314 (merged) and the update-check work in this PR. Key invariant specified: a stable client is never offered a prerelease. * test(semver): add CsCheck property-based tests for SemVer Generate thousands of random valid SemVers and assert the comparator's correctness laws: parse-totality, antisymmetry, transitivity, IsNewer/compare consistency, build-metadata invariance, stable-outranks-prerelease, and numeric-below-alphanumeric precedence. Complements the fixed-example SemVerTests with algebraic coverage over a large random space. Adds CsCheck 4.7.0 (test-only) via central package management. * fix(update): address code-review findings on the channel-aware update check - doctor: inject DaemonConfig for the channel instead of hand-reading netclaw.json, so a non-string Daemon.UpdateChannel no longer crashes the whole `netclaw doctor` run (findings #1, #4). - update-check: drop the low-value 1h TTL and keep a plain last-result store for the daemon /status API (finding #3). Make `channel` required (no default) on EvaluateManifest + CheckForUpdateAsync so a forgotten arg can't silently fall back to stable (finding #9). - SemVer: parse numeric prerelease identifiers as long, matching the bash generator's unbounded ints (finding #6). - /status: report FullVersion in the no-check-yet branch for consistency with the post-check branch (finding #8). - dotted prerelease convention (beta.N): update docs/examples + widen the property-test generators + add example cases; the release version gate now rejects mixed identifiers like `beta1` so a non-dotted tag can't ship and silently mis-order the channel (finding #2). - conformance: extract the generator's precedence key to feeds/scripts/semver_key.py and assert BOTH it and the C# SemVer comparator order one shared fixture (feeds/scripts/semver-order.txt) — via a C# test and a CI check — so the two implementations can't drift (finding #7). Also updates the release-channels OpenSpec change (dotted-tag requirement plus risk/decision notes).
Aaronontheweb
referenced
this pull request
in Aaronontheweb/netclaw
Jun 15, 2026
Resolves the 11 findings from the /code-review pass: #1 Multi-line secret redaction: per-line redaction in JobOutputLog misses secrets spanning lines (e.g. PEM blocks). Re-redact the assembled tail at every LLM-surface point (execution-actor completion, manager HandleQuery, NotifyLostJob) so multi-line secrets can't reach the model. #2 Journaled reap event (SessionBackgroundJobsReaped): reap marks were snapshot-only and lost on recovery when the passivation snapshot is skipped (parked approval), rehydrating killed jobs as 'running'. FinishJobReap now persists the reap; recovery replays it. Full serializer plumbing + round-trip test. #3 Dispose the Process in BackgroundJobExecutionActor.PostStop — stops the kernel handle / wait-handle leak (amplified by the no-default-timeout). netclaw-dev#4 Audience-gate the [active-background-jobs] block (commands, rationales, and the output-log path) for Public, matching WorkingContext. netclaw-dev#5 JobOutputLog.ReadTail falls back to the rotated .1 file when the current log is momentarily absent mid-rotation, instead of returning an empty tail. netclaw-dev#6 A transient File.Move failure in Rotate() is non-fatal: capture continues on the current log and retries next threshold, rather than permanently going silent. netclaw-dev#7 Back WriteFailure with a volatile field (un-gated fast-path read crosses threads). netclaw-dev#8 Correlate reap Ask replies with an epoch so a late reply from a superseded passivation can't resolve a newer handshake. netclaw-dev#10 Centralize the reap-reply handler (CommandJobReapResolved) across all non-terminal phases so a future phase can't silently drop the reply. netclaw-dev#11 Apply(TurnRecorded) now delegates job dedup/prune to the single shared CompleteTurnBackgroundJobBookkeeping helper so replay and live paths can't drift. netclaw-dev#9 AutoFlush is kept (live monitoring requires per-line visibility; a write() to the page cache is cheap and a time-throttle risks an unflushed quiescent ready-line) — documented as a deliberate decision. Tests: +6 (reaped-event round-trip, ReadTail rotation fallback + rethrow, SessionBackgroundJobsReaped apply, Public/Personal active-jobs gating); updated RotationFailure test to the new non-fatal contract. Full Actors suite 2412 green x2; slopwatch + headers clean.
Aaronontheweb
added a commit
that referenced
this pull request
Jun 15, 2026
… kill timer, reap on passivation (#1405) * Background jobs as detached processes: stream logs live, no default kill timer, reap on passivation, Lost notifications A background job is now a detached process with no expectation of completion (OpenSpec: background-jobs-detached-process-redesign). Fixes the hung-session class where a dev server (jekyll serve / npm run dev) could never be used: both execution paths blocked on process exit. - Stream stdout/stderr to ~/.netclaw/jobs/{id}/output.log while the process runs (per-line secret redaction, 5MB single-slot rotation). The existing check_background_job tail query and file_read/grep monitoring now work mid-run; output survives daemon crashes. Completion tails read from disk. - Remove the silent default kill timer on background routing: omitted _timeout_seconds now means no timer (was: synchronous default, killing un-hinted jobs early). Submit ACK includes the output log path. - Reap on session passivation: KillJobsForSession handshake before the final snapshot; new Reaped status (distinct from Cancelled); no turn delivery on reap (would rehydrate the session being torn down); reaped entries surface exactly once in [active-background-jobs] on rehydration, then prune. - Wire up session-side job tracking (TrackBackgroundJob had no production caller — the active-jobs context block was always empty). - Daemon-restart reconciliation now delivers Lost notifications to owning sessions with the pre-crash log path. - Remove the vestigial pending-approval passivation deferral: approvals are journaled and the response path already rehydrates and resumes. - AGENTS.md template, netclaw-operations SKILL.md (v2.13.0), and the background-jobs runbook document the new lifecycle; eval suite gains a background-job lifecycle regression case. * Fix background-job lifecycle eval: multi-turn harness, pre-trusted verb, tightened assertion The new tool_background_job_lifecycle case scored 0/5 for instrumentation reasons, not model behavior (per the eval-debugging guidance): 1. run_case treats multiple prompts as alternate phrasings (pick_variant) — sequential conversations need run_multi_turn_case, which resumes one session and accumulates stdout across turns for the assertion. 2. Even then, every background submission died at the approval gate: the headless eval container has no approval requester and 'sleep' is not on the safe-command allowlist. Passing runs were vacuous (the model probed check_background_job with a made-up ID while flailing). The eval setup now pre-trusts the sleep verb via 'netclaw approvals trust-verb' against the bind-mounted tool-approvals.json before the container starts, so the case exercises the real lifecycle: submit -> job id -> status -> cancel. 3. The assertion now requires the actual _background":true submission, not just any shell_execute call. Result: 5/5, with transcripts showing the genuine flow (job id returned, ACK steering to the streaming log path, live status with elapsed time, cancel confirmed). * Fix CI: SW003 empty-catch marker, parallel-test isolation for real-process job tests Two PR CI failures: 1. Slopwatch SW003 — the write-failure path in JobOutputLog had an empty inner catch with the rationale as a body comment instead of the repo's 'catch // slopwatch-ignore: SW003 <reason>' marker convention. (Passed locally because slopwatch 0.4.1 only scans the git diff vs local HEAD; CI's PR-merge scans the whole new file.) 2. Test-ubuntu-latest flake — KillJobsForSession_ReapsOwnedJobs and BackgroundJob_Completes_And_DeliversResult_ViaGateway intermittently failed with the owning manager's freshly-created jobs showing 'Lost'. Root cause (reproduced reliably by running the Jobs test classes together): under heavy parallel load, concurrent process/FS pressure makes a manager's message handler throw transiently, the actor restarts, and startup reconciliation correctly marks its in-flight jobs Lost — a spurious restart to induce in a unit test. Fix: serialize the three real-process-spawning job test classes via a DisableParallelization collection (repo's established pattern) so they don't mutually starve. Verified: full assembly 4/4 green, the prior ~Jobs repro 3/3 green. Also register TimeProvider in LlmSessionTestBase to mirror production DI (Daemon Program.cs) — WithNetclawActors() constructs the background-job and reminder managers via the DI resolver, which need it; without it they died with ActorInitializationException at startup, adding restart churn. * Address code-review findings on background-jobs feature Resolves the 11 findings from the /code-review pass: #1 Multi-line secret redaction: per-line redaction in JobOutputLog misses secrets spanning lines (e.g. PEM blocks). Re-redact the assembled tail at every LLM-surface point (execution-actor completion, manager HandleQuery, NotifyLostJob) so multi-line secrets can't reach the model. #2 Journaled reap event (SessionBackgroundJobsReaped): reap marks were snapshot-only and lost on recovery when the passivation snapshot is skipped (parked approval), rehydrating killed jobs as 'running'. FinishJobReap now persists the reap; recovery replays it. Full serializer plumbing + round-trip test. #3 Dispose the Process in BackgroundJobExecutionActor.PostStop — stops the kernel handle / wait-handle leak (amplified by the no-default-timeout). #4 Audience-gate the [active-background-jobs] block (commands, rationales, and the output-log path) for Public, matching WorkingContext. #5 JobOutputLog.ReadTail falls back to the rotated .1 file when the current log is momentarily absent mid-rotation, instead of returning an empty tail. #6 A transient File.Move failure in Rotate() is non-fatal: capture continues on the current log and retries next threshold, rather than permanently going silent. #7 Back WriteFailure with a volatile field (un-gated fast-path read crosses threads). #8 Correlate reap Ask replies with an epoch so a late reply from a superseded passivation can't resolve a newer handshake. #10 Centralize the reap-reply handler (CommandJobReapResolved) across all non-terminal phases so a future phase can't silently drop the reply. #11 Apply(TurnRecorded) now delegates job dedup/prune to the single shared CompleteTurnBackgroundJobBookkeeping helper so replay and live paths can't drift. #9 AutoFlush is kept (live monitoring requires per-line visibility; a write() to the page cache is cheap and a time-throttle risks an unflushed quiescent ready-line) — documented as a deliberate decision. Tests: +6 (reaped-event round-trip, ReadTail rotation fallback + rethrow, SessionBackgroundJobsReaped apply, Public/Personal active-jobs gating); updated RotationFailure test to the new non-fatal contract. Full Actors suite 2412 green x2; slopwatch + headers clean. * Fix racy ReminderManagerActorTests.Startup_emits_alert_for_legacy_reminder_missing_trust_fields Root cause (per akka-net + dotnet-concurrency analysis): the legacy-schema alert is emitted synchronously inside the actor's PreStart, and the test waited for it with a fixed 5s AwaitAssertAsync poll. Under heavy parallel CI load the shared ThreadPool is saturated (many TestKit ActorSystems, WithSerializationVerification overhead), so the actor's PreStart can be scheduled later than the 5s budget and the poll gives up with an empty sink. Not a logic/visibility bug — the sink is lock-guarded and the store records the rejection synchronously in its constructor. Fix: await a deterministic readiness signal instead of polling a wall clock. An actor processes mailbox messages only after PreStart completes, so a successful Ask<ReminderHealthResponse>(GetReminderHealthQuery) reply guarantees the emit has run. This is the same readiness pattern already used elsewhere in this test file; the generous Ask timeout absorbs scheduling latency and returns as soon as the actor is ready (no wasted time in the common case). No existing GitHub issue covers this test. Does not reproduce locally even at full-assembly parallelism (CI-runner-only starvation).
Aaronontheweb
referenced
this pull request
in Aaronontheweb/netclaw
Jun 17, 2026
…et errors Follow-up hardening from the code review of the Channels async-write migration. Closes the new exposures that the fire-and-forget writes introduced, plus adjacent findings: - Lifecycle cancellation (review #1/netclaw-dev#5): add a VM-lifetime CancellationTokenSource, thread its token through the AddChannel / Reset / autosave entry points, and have Dispose cancel it and DRAIN the in-flight write + label refresh before disposing the reactive state they publish to. Pre-fix, closing the editor mid-write could let the write resume on a thread-pool continuation and mutate a disposed ReactiveProperty / Step; the runtime path also passed CancellationToken.None, so nothing honored cancellation. - Reset fails loud (review #3): ApplyResetConfirmationAsync now surfaces ANY unexpected exception (e.g. an InvalidOperationException from a type-malformed config reload) as an Error status instead of letting it fault the fire-and-forget chain, where the next write's catch swallowed it silently. Honors the no-silent-fallbacks rule. - Search re-entrancy (review netclaw-dev#6): SubmitCurrentConfigurationFromInputAsync now ignores a re-entrant submit while a probe is in flight (returns the same task), closing the same two-rapid-submits-race-the-disk-write hazard the Channels write chain fixed. - Cleanup: collapse SaveCompletedAsync / SaveFromInputAsync onto one SaveViaAutosaveAsync helper (netclaw-dev#8); extract the shared ArrangeSlackResetWithLabelRefreshInFlight test setup (netclaw-dev#9); the single-worker SynchronizationContext test pump no longer dies on a throwing continuation, which would mask a real failure as a generic deadlock (netclaw-dev#7). Tests: a dispose-drains-an-in-flight-write test (Channels) and a re-entrancy test (Search). Deferred (pre-existing, tracked separately): the background label refresh and the save+reload tail still mutate view-model state off the loop, so a concurrent on-loop edit during a network resolve can lose an audience edit (review #2/netclaw-dev#4). Eliminating that requires marshalling those mutations back onto the loop (Termina exposes only RequestRedraw) and is its own change; the lifetime cancellation here bounds the window and closes the disposal portion.
Aaronontheweb
referenced
this pull request
in Aaronontheweb/netclaw
Jun 17, 2026
…et errors Follow-up hardening from the code review of the Channels async-write migration. Closes the new exposures that the fire-and-forget writes introduced, plus adjacent findings: - Lifecycle cancellation (review #1/netclaw-dev#5): add a VM-lifetime CancellationTokenSource, thread its token through the AddChannel / Reset / autosave entry points, and have Dispose cancel it and DRAIN the in-flight write + label refresh before disposing the reactive state they publish to. Pre-fix, closing the editor mid-write could let the write resume on a thread-pool continuation and mutate a disposed ReactiveProperty / Step; the runtime path also passed CancellationToken.None, so nothing honored cancellation. - Reset fails loud (review #3): ApplyResetConfirmationAsync now surfaces ANY unexpected exception (e.g. an InvalidOperationException from a type-malformed config reload) as an Error status instead of letting it fault the fire-and-forget chain, where the next write's catch swallowed it silently. Honors the no-silent-fallbacks rule. - Search re-entrancy (review netclaw-dev#6): SubmitCurrentConfigurationFromInputAsync now ignores a re-entrant submit while a probe is in flight (returns the same task), closing the same two-rapid-submits-race-the-disk-write hazard the Channels write chain fixed. - Cleanup: collapse SaveCompletedAsync / SaveFromInputAsync onto one SaveViaAutosaveAsync helper (netclaw-dev#8); extract the shared ArrangeSlackResetWithLabelRefreshInFlight test setup (netclaw-dev#9); the single-worker SynchronizationContext test pump no longer dies on a throwing continuation, which would mask a real failure as a generic deadlock (netclaw-dev#7). Tests: a dispose-drains-an-in-flight-write test (Channels) and a re-entrancy test (Search). Deferred (pre-existing, tracked separately): the background label refresh and the save+reload tail still mutate view-model state off the loop, so a concurrent on-loop edit during a network resolve can lose an audience edit (review #2/netclaw-dev#4). Eliminating that requires marshalling those mutations back onto the loop (Termina exposes only RequestRedraw) and is its own change; the lifetime cancellation here bounds the window and closes the disposal portion.
This was referenced Jun 19, 2026
Closed
Aaronontheweb
added a commit
that referenced
this pull request
Jun 30, 2026
…p-accurate roll Code-review #1 (the dominant finding) + #3, per the daemon.log decision. The field-based router diverted ANY line carrying a {SessionId} into that session's session.log — including daemon-infrastructure errors that merely NAME a session (gateway "failed to mark session X active", drain "failed to drain session X before shutdown", restart-recovery). Those are daemon functionality, and the class contract promises them in daemon.log. On dev they went there because the old AsyncLocal was only set inside session-serving code. Restore that structurally: a state SessionId routes only when it rode in on an Akka actor context (the bridge's AkkaLogState carries a LogSource) or a BeginScope (chat-client decorators, spawn breadcrumbs). A bare {SessionId} message field on a plain daemon-service ILogger<T> has no LogSource → stays in daemon.log. No hand-maintained category list. Swept all {SessionId} message templates: only daemon/channel/infra lines lack an actor context (consistent with dev); the one session-serving exception (tool-denial) was already moved to a scope. Also: daemon.log size-roll flushes within a batch of the 10MB cap so AutoFlush=off buffered bytes can't overshoot it (#3). Tests updated to model actor-context vs message-field lines, plus a new case locking in the daemon-service → daemon.log behavior. Skill bumped once for the PR (2.22.0).
Aaronontheweb
added a commit
that referenced
this pull request
Jul 1, 2026
…per session, daemon.log sparse, OTEL the union (#1472) (#1499) * refactor(logging): route session logs off the log event, delete the AsyncLocal (#1472 step 6) Actor ILoggingAdapter lifecycle lines now reach per-session session.log reliably, and the SessionDiagnosticsContext AsyncLocal threading is gone. The session.log sink (RollingFileLoggerProvider) reads the session id off each log event's structured state instead of an ambient AsyncLocal. The Akka->MEL bridge passes the event's properties as the MEL state (AkkaLogState, carrying the actor's WithContext("SessionId", ...) tag), and MEL's own structured logging passes FormattedLogValues (a {SessionId} field). RollingFileLogger.Log reads "SessionId" from either and Enqueue(message, sessionId) routes to the existing per-session writer (SessionLogDiagnostic -> SessionLogDispatcher -> SessionLogActor). Because the id rides on the line, routing no longer depends on thread/async context — a line logged after an await still routes, which the AsyncLocal could not. Deleted (the context-threading workaround): - SessionDiagnosticsContext (the AsyncLocal) + its 9 Push() scope sites. - SessionLoggingScope (the MEL BeginScope helper) + its 3 chat-client call sites. - Obsolete tests/helpers: SidecarDiagnosticsContextTests, ScopeCapturingLogger, the SessionDiagnosticsContext test. NormalizeSessionId relocated to SubAgentSessionScope. Kept session-routed by carrying the id on the line: - The #1468 spawn breadcrumbs (SubAgentSpawner/SpawnAgentTool) now include a {SessionId} field, so the sink routes them without a scope. Behavior change: chat-client / LLM-pipeline INTERNAL diagnostics (retry, failover, request/response internals) were only ever session-tagged via the AsyncLocal — already lost after the first await — so they settle into daemon.log only. Each actor's own lifecycle log still reaches session.log via the sink, and OTLP/Seq correlation for actor logs is unaffected (it rides the same WithContext tag). Net -350 LOC. The bridge behavior (AkkaLogState carrying the WithContext properties) was verified by decompiling Akka.Hosting 1.5.69; provider-level + async-continuation tests cover the read path. Skill diagnostics guidance updated (netclaw-operations 2.19.0). Part of #1472. * test(logging): end-to-end proof actor _log routes to session.log via the real bridge (#1472 step 6) A hand-built host (AddLogging(AddProvider) + AddAkka(ConfigureLoggers(AddLoggerFactory)) — production wiring) drives an actor's WithContext("SessionId", ...) log through the real Akka->MEL bridge into RollingFileLoggerProvider and asserts a SessionLogDiagnostic is routed for that session. Proves the essential goal through the live bridge, not a mocked state. Not Akka.Hosting.TestKit on purpose: its bridge (TestKitLoggerFactoryLogger : the production LoggerFactoryLogger) is hardwired to the TestKit's own xUnit test-output ILoggerFactory, so a TestKit-based test cannot observe the bridged log arriving at our provider. Relates to #1499, #1472. * refactor(logging): publish session logs explicitly instead of routing in the sink A max-effort review showed the previous step-6 approach (the file sink reading a "SessionId" property off each log event and routing it to session.log) was wrong: "{SessionId}" is a descriptive message-template placeholder in 86 places across the codebase (binding actors, gateway, webhooks, session catalog/registry, drain/recovery), each of which produces a structured "SessionId" property — so the sink would flood session.log with operational noise and create a self-feedback loop in SessionLogActor's write-failure path. SessionId is a correlation field, not a routing field. Replace sink-side routing with explicit publish through the existing actor-message contract (the same one that already carries the transcript): - RollingFileLoggerProvider reverts to a daemon.log-only writer — no ExtractSessionId, no dispatcher attach, no buffer. (Deletes the collision, the feedback loop, the per-line boxing, the dead branch, and the TOCTOU buffer race the review found.) - ToolExecutionContext.EmitSessionLogLine: LlmSessionActor wires it to _logActor.Tell(new SessionLogDiagnostic(parentSessionId, line)); the tool pipeline threads it to the spawn breadcrumbs, which publish their lifecycle explicitly. Using the parent id also fixes the composite-id normalization gap. - SessionId stays a pure Seq/OTLP correlation field on those lines, doing one job. - Removed the dead sessionId param on SubAgentActor.InvokeLlmAsync; fixed the stale LoggingChatClient doc; skill diagnostics guidance updated to the explicit-publish model. session.log scope = transcript + explicitly-published spawn lifecycle. Chat-client and sidecar internals are daemon.log-only (filter by the SessionId field). Sidecar diagnostic restore is a follow-up. Relates to #1499, #1472. * fix(logging): publish routed-skill sub-agent spawn lifecycle to session.log (#1472 step 6) The routed-skill spawn path (LlmSessionActor.ExecuteRoutedSkillAsync) built its own ToolExecutionContext and wired SpawnChildActor + OnSubAgentActivity but never set EmitSessionLogLine, so skill-routed sub-agent spawns — and critically their failure breadcrumbs — were invisible in session.log. That is the original #1467 gap still present on one of the three spawn paths. Wire EmitSessionLogLine to the session-log dispatcher, mirroring the tool-execution dispatch path, and fix the stale SessionLogDiagnostic doc comment that still described the deleted MEL-sink-routing model. Test: Routed_slash_command_publishes_spawn_lifecycle_to_session_log (verified failing without the fix). * feat(logging): correlate LLM chat-client diagnostics to sessions via explicit ChatOptions Deleting the SessionDiagnosticsContext AsyncLocal (#1472 step 6) removed the only way the session-agnostic chat-client decorators (logging / retry / routing) knew which session a call belonged to. LLM timing, retries, and — most importantly — provider failover/outage events lost all session correlation: gone from session.log (intentional) and carrying no SessionId field in Seq either. Restore the Seq/OTLP correlation explicitly, without resurrecting ambient state that does not flow across the actor mailbox boundary: - SessionScopedChatOptions carries the owning session id on the call's ChatOptions as a typed property — NOT in AdditionalProperties, which the self-hosted provider forwards verbatim onto the wire (a dictionary entry would leak the id into the LLM request body). LlmSessionActor and SubAgentActor (parent id) set it on every model call; sidecar calls stay session-agnostic. - ChatClientSessionScope re-opens the SessionId logging scope in all three decorators, sourced from the options. IncludeScopes surfaces it as a filterable Seq field; session.log stays narrow (no re-flood). Tests: decorator scope attach + negatives, a wire-leak guard proving the id never reaches the provider request body, and builder-side coverage that the session and sub-agent actors emit the carrier with the right id. Updates the netclaw-operations diagnostics skill (2.20.0) to state precisely where each line type is filterable (inline grep vs Seq scope). * refactor(logging): address code-review findings on session-log correlation Follow-up to the two preceding commits, from a multi-agent review pass: - Dedupe the explicit session-log emitter: the routed-skill path and the tool-execution dispatch path built the identical logActor?.Tell(SessionLogDiagnostic(...)) closure. Collapse both onto one private CreateSessionLogEmitter() so their timestamp format and routing cannot drift. - Drop a now-dead null-conditional (options?.Tools) — options is unconditionally non-null since it became the SessionScopedChatOptions carrier. - Cover the production (streaming) path: the chat-client scope tests only exercised the non-streaming GetResponseAsync branch, but StreamingResponseReader only ever calls GetStreamingResponseAsync. Add streaming retry-backoff and provider-failover scope tests, plus a composed-pipeline test proving the SessionScopedChatOptions subclass survives Logging->Retry by reference (guards the correlation against a future cloning decorator). - Correct the diagnostics skill: session id is reliably filterable in Seq for both actor and decorator lines; daemon.log *text* grep only catches lines that template the id inline. The prior wording over-claimed inline greppability for context-enriched actor logs. * refactor(logging): centralize the "SessionId"/"SubSessionId" log-attribute keys The structured-logging key "SessionId" was hardcoded as a raw literal at ~9 WithContext sites across the actor system and every channel adapter, plus two private SessionIdKey consts in the chat-client scope helper and its test double. A rename to align OTLP attributes would have had to find every copy, and the producer could silently drift from the test assertion. Introduce Netclaw.Actors.Protocol.NetclawLogProperties (SessionId, SubSessionId) — Netclaw.Actors is already referenced by every consumer (Daemon, all Channels.*, Daemon.Tests), so no new project references — and route all logging-key usages through it. Type references, proto field names, DTO/JSON keys, and doc-comment prose are untouched; only WithContext/BeginScope keys changed. * refactor(logging): drop RetryingChatClient's redundant SessionId scope Code-review follow-up. In the composed pipeline RetryingChatClient always runs inside LoggingChatClient's streaming scope (guarded by Compose_puts_Logging_ outermost), which stays open for the whole enumeration that drives the retry loop — so BackoffAsync re-opening an identical SessionId scope was pure duplication. Drop it and document the inherited-scope contract; only RoutingChatClient, which logs outside any LoggingChatClient scope, keeps its own. Move the retry-warning correlation coverage from two isolated-decorator tests (which asserted the now-removed self-scope) to one composed-pipeline test that fires a real retry and asserts, at the message level, that the warning line is emitted while the SessionId scope is active — i.e. it tests the production wiring (LoggingChatClient enclosing Retry) rather than a decorator in isolation. * refactor(logging): collapse sub-agent spawn breadcrumbs to one fan-out emitter Every spawn-lifecycle breadcrumb hand-drove two sinks at the call site — a structured _logger call for daemon.log/Seq and a parallel EmitSessionLogLine string for the session.log transcript — with two message strings to keep in sync. The sinks are genuinely different artifacts (operational diagnostics vs the per-session audit transcript; unifying them at the sink was tried and reverted), but that seam belongs behind one emit point, not duplicated at every site. Add SubAgentSpawnBreadcrumbs: one method per lifecycle event that fans out to both sinks from a single source of truth. SubAgentSpawner (7 sites) and SpawnAgentTool (2 refusal sites) now state each event once; the two renderings live together and can't drift. Message text is byte-for-byte preserved, so the existing SubAgentSpawnObservabilityTests and the routed-slash session-log test cover it unchanged. * feat(logging): partition the log stream by session instead of explicit-publish Replace the bolted-on EmitSessionLogLine side-channel with a single partitioning logging sink. The routing rule is now simply "does this line carry a session id?": - RollingFileLoggerProvider implements ISupportExternalScope and, per line, resolves the session id from the event's structured state (an actor's WithContext("SessionId", ...) bridged by Akka->MEL, or a {SessionId} message field) or from the active scopes (the chat-client decorators' BeginScope). A session-tagged line is Tell'd to the SessionLogDispatcher as a SessionLogDiagnostic and is NOT written to daemon.log; everything else goes to daemon.log. The full stream still exports to OTEL with SessionId as an attribute — the receiver does the global slicing. - The session-log writer's own lines (identified by its Akka LogSource/ActorPath) are forced to daemon.log so a write-failure log can never recurse into the file that just failed. - session.log is now the session's full local slice (transcript + every session-scoped operational line), so SessionLogActor switches from per-line AutoFlush to a batched/idle flush (~1s or 256 writes). FlushTick is INotInfluenceReceiveTimeout so the cadence doesn't keep an idle session alive. Deletes ToolExecutionContext.EmitSessionLogLine, LlmSessionActor.CreateSessionLogEmitter, and the pipeline threading. SubAgentSpawnBreadcrumbs now logs each event under a SessionId scope (one call, no fan-out); because routing is no longer per-path-wired, the original routed-skill gap (commit 9551067) is structurally impossible. Retains the chat-client SessionId-scope work, which is now what routes LLM-pipeline lines. Restores the SessionLogDispatcherWiringService IHostedService to attach the dispatcher post-start. Tests: RollingFileLoggerPartitionTests (routing, scope, feedback carve-out, pre-attach buffering); SubAgentSpawnObservabilityTests reworked to assert the SessionId scope; diagnostics skill updated to 2.21.0. * test(logging): end-to-end partition proof through the real Akka->MEL bridge The unit tests simulate the log-event state shape; this drives the real thing. A test actor tags its logger with WithContext("SessionId", ...) and logs; the line flows through ConfigureLoggers(AddLoggerFactory) -> the host ILoggerFactory -> RollingFileLoggerProvider, which extracts the id from the live AkkaLogState and routes it to the real SessionLogDispatcher/SessionLogActor. Asserts the line lands in that session's session.log file and NOT in daemon.log — confirming the bridge produces the structured state the provider's ScanState reads. * fix(logging): harden the session-partition sink (code-review follow-up) xhigh review fixes on the partition rework: - Concurrency in RollingFileLoggerProvider's pre-resolution buffer had a null-deref (NRE thrown into a logging call) and a lost-line race against dispatcher resolution. Replaced the Interlocked/ConcurrentQueue dance with a lock-guarded slow path; the steady state (dispatcher resolved) stays lock-free via a Volatile read. On resolution FAILURE the buffered lines now drain to daemon.log instead of being dropped, and the one-shot failure beacon has a stderr fallback if the daemon queue is saturated. - SessionLogActor flush failure: Flush() now resets _unflushedWrites and rate-limits the warning to onset+recovery, so a persistent disk error no longer floods daemon.log every tick. Its own self-logs use a {Session} key (not {SessionId}) so they can't arm routing back into the file that just failed — defense-in-depth on the feedback carve-out. - FindSessionIdInScopes: static lambda + StrongBox state to drop the per-line closure allocation on the chat-client hot path. - Docs: fixed the LoggingChatClient comment (its lines DO route to session.log now) and the diagnostics runbook (a daemon-wide provider outage surfaces as the sessionless provider.unreachable alert in daemon.log; per-call failover detail is in each session's session.log). Test: Dispatcher_resolution_failure_drains_buffer_and_beacons_to_daemon_log. * docs(logging): document the dormant non-streaming retry session-scope gap RetryingChatClient inherits its SessionId scope from the enclosing LoggingChatClient, which instruments only the streaming path. The non-streaming GetResponseAsync retry path therefore opens no scope — dormant today (Netclaw issues only streaming requests), but if a non-streaming path is ever added the retry warnings would lose session correlation and route to daemon.log. Leave a caveat at BackoffAsync pointing the future fix at ChatClientSessionScope.Begin / LoggingChatClient.GetResponseAsync. * refactor(logging): drop the startup buffer + lock for a single volatile dispatcher ref The pre-resolution buffer (and the lock guarding it, where the review found a null-deref and a lost-line race) only existed to route session-tagged lines emitted in the window between AttachSessionDispatcher and the dispatcher resolving. But the things that log with a SessionId — session actors, chat-client calls — don't exist until a session starts processing a turn, well after startup, so that window carries no session traffic. Collapse it to one volatile _sessionDispatcher reference that IRequiredActor.GetAsync resolves in the background; a line that finds it still null (startup window or resolution failure) falls back to daemon.log, which is already the documented routing-off behavior. Removes _pendingDiagnostics, the _routeGate lock, _routingFailed, _sessionRoutingEnabled, and the whole concurrency surface the review flagged (~60 lines). Resolution stays in the background — not a blocking await in the wiring service's StartAsync, which runs before Akka and would deadlock. Tests updated: a pre-resolution session line now falls back to daemon.log rather than buffer-and-drain. * fix(logging): breadcrumb emitters take SubAgentRunId after the merged run-id refactor The merged SubAgentRunId/SubAgentScopeId refactor changed SubAgentSpawner's runId from a string to a SubAgentRunId value object, but the spawn breadcrumb emitters still took `string runId` — a CI compile failure (CS1503). Take SubAgentRunId and log runId.Value. * feat(logging): give each sub-agent run its own session.log, keyed by SubSessionId Per the design call: a parent investigating a sub-agent reviews that run's own log, and the parent's session.log stays clean (transcript + spawn breadcrumbs only). - RollingFileLoggerProvider reads SubSessionId (from log state or scope) and partitions the LOCAL file by SubSessionId when present, else SessionId. The line still carries the parent SessionId, so OTEL groups sub-agent lines under the parent AND slices by SubSessionId. - SessionScopedChatOptions gains SubSessionId; ChatClientSessionScope emits it in the scope; SubAgentActor sets it (its scopeId {parent}/subagent/{name}/{runId}) on the LLM-call options. The sub-agent's own actor logs already carried both ids. - Spawn breadcrumbs stay in the PARENT's log (parent context, no SubSessionId) as the pointer. Tests: provider routes a state- or scope-carried SubSessionId line to the sub-run file (not the parent's). diagnostics skill updated (2.22.0). * fix(logging): restore session correlation for sidecar LLM paths (code-review) The AsyncLocal removal stripped SessionDiagnosticsContext from five session-owned sidecar chat calls but never gave them the replacement SessionScopedChatOptions carrier, so their chat-client diagnostics (timing, retries, provider failover) silently regressed to daemon.log uncorrelated — and the #920 guard test was deleted in the same commit. Restore correlation by threading SessionScopedChatOptions through all five (each already had the session id in scope): SessionTitleGenerator, SessionCompactionPipeline observer, SessionMemoryObserverActor distillation, MemoryCurationActor, and LlmSessionActor memory-extraction. Replace the deleted guard with SidecarSessionCorrelationTests (one case per sidecar, asserting the carrier). The diagnostics doc's claim that session.log holds the session's LLM-pipeline/memory lines is now true rather than aspirational. Also from the review: - Harden RollingFileLoggerProvider.Route: only consult scopes when there is no state session id, so an ambient SubSessionId scope cannot hijack an actor line's routing. - SessionLogActor.Flush: keep retrying on the 1s tick while failing, so buffered audit bytes are not stranded through an idle period after the disk recovers. - Correct SubAgentSessionScope doc: it claimed sub-agents reuse the parent's log and were the "only place" the /subagent/ split happens — both now false (own file; ToolApprovalActor too). * feat(logging): flush the audit transcript immediately, keep diagnostics batched Per the durability decision: SessionLogActor has two inputs — the audit transcript (user/assistant/tool/usage via SendUserMessage/SessionOutput, low volume, durability- critical) and the high-volume diagnostic lines (SessionLogDiagnostic). The audit handlers now WriteDurable (write + immediate flush) so a hard process death (SIGKILL/OOM) cannot drop the audit record's tail, while diagnostics stay on the batched Write() path (an fsync per line would dominate now that the whole per-session stream lands here). An audit line also flushes any diagnostics buffered before it, so audit lines are natural flush points. * refactor(logging): batch daemon.log flushes + consolidate the /subagent/ split (code-review) Two minor code-review follow-ups: - daemon.log writer no longer fsyncs per line. It flushes when the queue drains (so sparse daemon.log lines — startup, config, lifecycle, alerts — stay immediately durable) or after a 256-line burst cap, matching the cadence the sibling SessionLogActor already uses. The tail is flushed by Dispose() when the writer thread exits. - ToolApprovalActor's approval-inheritance walk now calls SubAgentSessionScope.NormalizeSessionId instead of re-implementing the "/subagent/" split inline, so there is a single owner of that parse (behavior preserved — IndexOf-first already walked leaf -> root parent). Doc updated. * fix(logging): guard PostStop dispose, fix audit-durability doc, scope the tool-denial line Code-review follow-ups (clear-cut findings): - SessionLogActor.PostStop: guard _writer.Dispose() in try/catch. On a disk-full stop the explicit Flush() already reports via its rate-limited warning; the Dispose flush could throw IOException out of PostStop and bury the real cause under Akka's PostStop-failed noise. - diagnostics skill: the flush description claimed session.log is uniformly batched (~1s lag). It now documents the split — the conversation audit is flushed immediately (durable), only the diagnostics batch. (skill 2.23.0; System Skills Sync Rule.) - SubAgentSpawner tool-denial line: route via SubAgentSpawnBreadcrumbs.ToolDenied (a SessionId scope) instead of a hand-rolled "(session={SessionId})" message field, so it can't drift from the centralized NetclawLogProperties key like every other breadcrumb. * fix(logging): keep daemon-infra session-named lines in daemon.log; cap-accurate roll Code-review #1 (the dominant finding) + #3, per the daemon.log decision. The field-based router diverted ANY line carrying a {SessionId} into that session's session.log — including daemon-infrastructure errors that merely NAME a session (gateway "failed to mark session X active", drain "failed to drain session X before shutdown", restart-recovery). Those are daemon functionality, and the class contract promises them in daemon.log. On dev they went there because the old AsyncLocal was only set inside session-serving code. Restore that structurally: a state SessionId routes only when it rode in on an Akka actor context (the bridge's AkkaLogState carries a LogSource) or a BeginScope (chat-client decorators, spawn breadcrumbs). A bare {SessionId} message field on a plain daemon-service ILogger<T> has no LogSource → stays in daemon.log. No hand-maintained category list. Swept all {SessionId} message templates: only daemon/channel/infra lines lack an actor context (consistent with dev); the one session-serving exception (tool-denial) was already moved to a scope. Also: daemon.log size-roll flushes within a batch of the 10MB cap so AutoFlush=off buffered bytes can't overshoot it (#3). Tests updated to model actor-context vs message-field lines, plus a new case locking in the daemon-service → daemon.log behavior. Skill bumped once for the PR (2.22.0). * test(logging): address code-quality bot findings (Path.Join, drain-loop comments) GitHub code-quality (Copilot) review follow-ups on this PR's test files: - Path.Combine -> Path.Join in the logging test temp-path helpers: the CodeQL "may drop earlier arguments" rule can't prove the second segment is non-rooted; Join is the correct idiom for plain concatenation and clears the recurring finding (identical result for these inputs). - Intent comments on the empty `await foreach (var _ in stream) { }` drain loops in the chat-client tests, so the enumerate-and-discard reads as intentional (it drives the pipeline's scope/retry/logging side effects, which the test then asserts). Declined the "use .Where()" suggestions: on the provider hot path (FindScopeIds) a LINQ Where allocates a per-line iterator/closure that the manual loop + static lambda deliberately avoids (documented); the mirroring test helpers stay foreach for consistency with it.
Aaronontheweb
added a commit
that referenced
this pull request
Jul 4, 2026
…lently defeats matching) (#1576) * fix(evals): grep transcripts with -a — invalid UTF-8 was silently defeating assertions GNU grep in a UTF-8 locale silently skips lines with invalid multibyte sequences when the -a flag is absent. Eval transcripts contain terminal control bytes and other non-UTF-8 sequences, causing assertion helpers to randomly fail when escape bytes landed on a matching line — the mechanism explains the historic flakiness of memory_identity_preference_routing (0/5→3/5→5/5→3/5 swings). Fixed all 14 grep call sites in the assertion helpers and inline transcript assertions by adding -a: - stdout_contains, stdout_not_contains (grep -qi → grep -qia) - stdout_response_contains, stdout_response_not_contains (both pipes: -v → -av, -qi → -qia) - daemon_log_contains, daemon_log_skill_loaded, daemon_log_no_skill_loaded (grep -qE → grep -qaE) - stdout_tool_called (grep -qE → grep -qaE) - store_metrics: grep on usage_line (grep -oP → grep -aoP, 5 invocations) - run_prompt_resume: grep on turn_file (grep -o → grep -ao) - assert_multi_turn_text_growth: grep on STDOUT_FILE (grep -c → grep -ac) - assert_approval_set_working_directory_positive: grep -nE on STDOUT_FILE (2 invocations, → grep -anE) Proven on run af0883b5: memory_identity_preference_routing cases had correct behavior ('durable memories' literal text in transcript), but grep -ci returned 0, while grep -cia returned the correct count. This fix eliminates the instrumentation source of the flakiness. * fix(evals): match 'memor' in identity-routing assertion — 'memories' does not contain 'memory' The memory_identity_preference_routing assertion was failing intermittently because correct model responses often say 'memories' (plural), but the assertion required the literal substring 'memory' (singular). 'memories' does NOT contain 'memory' as a substring — the plural drops the y. Regression proof from run af0883b5: runs #3 and #5 exhibited correct behavior ("That's already saved — ... in my durable memories", no file edits, no redundant store call) but were rejected by the old pattern. The -a flags added in the prior commit are retained as cheap defensive hardening for transcripts that may carry terminal control bytes, but they are NOT the fix for this flake. The invalid-UTF-8 justification was incorrect and is retracted.
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.
Updated docfx from 2.78.3 to 2.78.4.
Release notes
Sourced from docfx's releases.
2.78.4
🚀 Release Notes
🌟 New Features
.NET 10nightly build support. (#10577, #10561)🐞 Bug Fixes
[Experimental]attribute support by default. (#10566)IOExceptiongracefully when file access conflicts occur. (#10786)textContentto correctly render header titles. (#10790)AnalyzerLoadFailedto improve debugging. (#10617)⚡ Performance Improvements
MarkdownPipelineinstance to reduce re-initialization overhead. (#10568)YamlDeserializerinstance for better memory efficiency. (#10567)📘 Documentation Updates
.slnxinclusion. (#10610)🙌 Contributors
Thanks to our awesome contributors for this release:
@filzrev, @Lombra, @frarees, @ettfemnio, @Lulalaby, @mgarstenauer, @anthony-c-martin, @clicktodev, @Copilot, and everyone who helped improve this release 💙
Commits viewable in compare view.
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)