…is live
Generated assistant media is stored with a null messageId and a transient
retention class until the assistant turn commits its transcript message and
attaches the record. The managed outgoing media GC reaped transient records
purely by age, so any turn still in flight past the 15 minute TTL had its
media records and files deleted before finalization; the later attach is
silently ineffective and the committed message keeps a permanently dead
media URL.
Cleanup now accepts a hasActiveSessionRun probe and skips the transient age
reap while the owning session still has a registered chat run. Both GC entry
points, the chat.history sweep and the maintenance sweep, supply the probe
from the gateway's registered chat run map, which spans admission through
finalization. Sessions without a live run keep the existing TTL behavior.
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
The compare-and-swap revision option was parsed with raw Number(), so
hexadecimal (0x2) and scientific (1e2) values were silently accepted
and used as the CAS revision even though the validation message
requires a non-negative integer. Delegate to the shared
parseStrictNonNegativeInteger helper used across the CLI so
non-decimal input fails fast and no write is attempted.
The skipped malformed transcript row warning prints the total count
followed by a row-number sample capped at 20, with no indication the
list is partial, so messages like "Skipped 25 ... rows 1, ..., 20"
read as if every skipped row were listed. Append an ellipsis when the
count exceeds the retained sample.
* fix: remove bodyless 400/413 from overflow patterns to prevent false compaction
PR #67024 fixed the failover classification path (errors.ts) to not
classify bodyless 400/422 as 'format' errors. However, the compaction
trigger path (isContextOverflow in overflow.ts) still has the pattern
/^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i in OVERFLOW_PATTERNS.
This means a bodyless 400 from any provider triggers compaction via
checkCompaction() -> isContextOverflow(), even though PR #67024
ensured the failover reason is null (not 'format'). The two paths are
independent: failover classification and compaction triggering use
separate functions.
A bodyless 400 cannot be a context overflow — real overflow errors
from all major providers include a descriptive body. A bare 400 with
no body is a transient/malformed error that should surface to the
user, not trigger a compaction loop.
Remove the Cerebras-specific pattern from OVERFLOW_PATTERNS. If
Cerebras returns bodyless 400/413 for actual overflow, it should be
handled with a more specific pattern or NON_OVERFLOW_PATTERNS exclusion.
AI-assisted.
* fix(ai): avoid compacting on bodyless 400 errors
---------
Co-authored-by: lykeion-dev <lykeion-dev@users.noreply.github.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
Classify direct and nested checkout roots through Git worktree registration before recursive orphan cleanup, including unborn and canonicalized paths. Fail closed when checkout metadata cannot be inspected.
Punchcard-Session: amber-meadow-timber-8r
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(diagnostics-otel): harness.run root span adopts diagnostic trace context
Fall back to internalOrTrustedParentContext when there is no active
tracked parent, so the openclaw.harness.run root span (and its whole
OTel subtree) adopts the diagnostic traceId instead of minting a new
OTel root. This lets spans propagated via the diagnostic traceparent
(e.g. a2a-gateway a2a.call / downstream a2a.task) share one trace with
the run internals.
* fix(diagnostics-otel): propagate exported span context
Co-authored-by: HughTang <44694613+HughTang@users.noreply.github.com>
* fix(diagnostics): break propagation import cycle
* fix(diagnostics): preserve trace propagation contracts
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(ui): preserve chat history and active run timing
* test(ui): prove reconnect preserves active timer
* fix(gateway): keep recovery fallback within budget
* test(gateway): keep chat abort coverage within lint budget
* docs(slack): drop retired socketMode transport tuning
channels.slack.socketMode is rejected by Slack config validation, pinned by
the 'rejects retired Socket Mode ping/pong transport tuning' schema test, and
the client pong timeout is now a hardcoded 15s constant. The page still told
operators to configure it.
* fix(slack): migrate retired socketMode with doctor instead of hand-editing
The retired object fails schema validation at root and account scope, so the
docs previously left manual removal as the only recovery. Slack already owns
legacyConfigRules and normalizeCompatibilityConfig, so the strip belongs there.
* test(slack): narrow the doctor contract config record for check-test-types
normalizeCompatibilityConfig returns a possibly-absent channel entry, so the
socketMode assertions need expectDefined before indexing.
* docs(slack): describe socketMode retirement by observed behavior
openclaw config validate reports a config carrying the retired object as valid,
so the earlier 'validation rejects it / fails to load' wording was wrong. The
observable facts are that it is not read and that doctor --fix removes it.
* revert(slack): drop the duplicate socketMode doctor migration
Core already strips channels.slack.socketMode at root and account scope in
legacy-config-migrations.runtime.retired.ts via visitChannelEntries, so the
plugin-side rule was duplicate policy. Keeps this PR to the docs correction.
* docs(slack): state what doctor actually cleans up for socketMode
Doctor flags retired layout knobs with a general notice, and --fix removes the
three named fields and drops the object only once empty, so an unknown key
inside it survives.