fix(trajectory): tolerate partial skill snapshot entries in support capture#71185
fix(trajectory): tolerate partial skill snapshot entries in support capture#71185lukeboyett wants to merge 2 commits into
Conversation
Greptile SummaryThis PR adds defensive guards to prevent crashes in The fallback logic, filtering, null-safe comparator, and widened Confidence Score: 5/5Safe to merge — defensive fix with no behavior change to the security-sensitive symlink-escape rejection path. All changes are narrowly scoped defensive guards on the support-capture consumer path. Filtering logic, fallback, and null-safety are all correct. Tests cover each failure mode. No P0 or P1 findings. No files require special attention. Reviews (1): Last reviewed commit: "fix(trajectory): tolerate partial skill ..." | Re-trigger Greptile |
|
@codex review |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Codex review: needs maintainer review before merge. Workflow note: Future ClawSweeper reviews update this same comment in place. How this review workflow works
Summary Reproducibility: yes. Source inspection shows a malformed PR rating What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. Real behavior proof Risk before merge
Maintainer options:
Next step before merge Security Review detailsBest possible solution: Land the focused consumer hardening once required checks are green or confirmed unrelated, and keep producer-side normalization of rejected skill entries as a separate cleanup if maintainers want a stricter contract. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows a malformed Is this the best way to solve the issue? Yes. Defensive tolerance in the support-capture consumers is the narrowest immediate fix for preventing trajectory metadata from failing agent turns while preserving the symlink-escape rejection behavior. Label justifications:
What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 384451343191. |
3b33589 to
17c521a
Compare
17c521a to
09b7957
Compare
Status update — rebased onto current
|
| Commit | Effect on this PR |
|---|---|
a4b97075ae fix: align support URL redaction |
Touches diagnostic-support-redaction.ts but only the URL-param regex; coexists with the redactPathForSupport signature widening here. |
e7432ae01d fix: redact URL query credentials in diagnostics |
Same file, URL-redaction work, no overlap. |
474bea162b fix: bound trajectory runtime flush (#77154) |
Touches runtime.ts, not metadata.ts. |
a2b84e98e9 fix: clean up trajectory sidecars |
Sidecar lifecycle, orthogonal to support-capture metadata. |
The crash this PR addresses (partial-entry leakage from the symlink-escape-rejection path through buildSkillsCapture / redactPathForSupport) hasn't been fixed elsewhere — producer-side src/agents/skills/workspace.ts still emits the partial shape, and no upstream commit has tightened that path or added a null-safe consumer guard.
Re-review
@codex please re-review the rebased commits. Main question: is the defensive consumer-side guard still the right shape given recent trajectory/support-redaction churn, or should the producer-side cleanup (drop partial entries at the symlink-escape rejection site) be the canonical fix?
|
Codex Review: Didn't find any major issues. What shall we delve into next? ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Addressed codex's P3 in Pushing as |
|
To use Codex here, create an environment for this repo. |
3ddda5d to
a4a96ff
Compare
a4a96ff to
b169d2c
Compare
b169d2c to
3a88a7a
Compare
3a88a7a to
e515467
Compare
Addresses codex P3 on openclaw#71185. OpenClaw policy requires a CHANGELOG.md entry when a PR changes a user-facing failure path; the trajectory/support-capture null-safety fix in this PR qualifies (every agent turn was failing with reason=unknown via the shared metadata builder). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
e515467 to
6c19dc1
Compare
|
ClawSweeper PR egg ✨ Hatched: 🥚 common Brave Test Hopper Hatch commandComment Hatchability rules:
Rarity: 🥚 common. What is this egg doing here?
|
…apture buildSkillsCapture and redactPathForSupport crash with "Cannot read properties of undefined (reading 'startsWith' | 'localeCompare')" when skillsSnapshot.resolvedSkills contains entries with undefined name/filePath/baseDir. This happens in practice when a skill is rejected by the symlink-escape guard in src/agents/skills/workspace.ts: the [skills] warn log fires, but a partial entry still reaches the support-capture path. Downstream, runEmbeddedAttempt builds the trajectory run metadata on every turn, the undefined fields hit String-method calls, and the entire agent turn fails with reason=unknown (burning through the model-fallback chain, which all share this code path). Defensive consumer fix: - redactPathForSupport: widen file arg to string | null | undefined, return "" for non-string input - buildSkillsCapture: filter entries without a string name; if every resolvedSkills entry is filtered out, fall back to skillsSnapshot.skills so support bundles don't lose the skill inventory entirely; null-safe localeCompare comparator Security posture unchanged -- the symlink-escape rejection in src/agents/skills/workspace.ts still refuses to load escaped symlinks. This PR only prevents the downstream support-capture consumer from crashing on the partial entries the rejection path leaves behind. A producer-side cleanup (making symlink-escape rejects either skip the entry or emit a placeholder name) is a reasonable follow-up but has broader ripple; keeping this PR tightly scoped. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
CI check-test-types was failing on src/trajectory/metadata.test.ts because
tsgo:test is stricter than local pnpm check. Rejected sourceInfo: {} and
bare 'undefined' assignments against typed SourceInfo / string fields.
Fill sourceInfo with a real SourceInfo shape for the valid 'alpha' entry,
cast the intentionally-partial entries to ResolvedSkillEntry via an
'as unknown as' bridge so the intent (simulating a symlink-escape reject
leaking through) stays explicit without weakening the type on the valid
branch.
Tests: 6 passed (unchanged). tsgo:core:test clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
6c19dc1 to
9d8e173
Compare
|
@clawsweeper automerge |
|
ClawSweeper 🐠 reef update Thanks for the work here. ClawSweeper could not write to the source branch, so it opened a replacement PR rather than letting the fix drift. attribution still points back here. Why replacement: ClawSweeper could not update the source PR branch directly; GitHub did not grant sufficient push rights to the bot for that branch.
fish notes: model gpt-5.5, reasoning high; reviewed against 99a204b. |
Summary
buildSkillsCaptureandredactPathForSupportcrash on skill snapshot entries with undefinedname/filePath/baseDir, which happens in practice whenever the symlink-escape guard rejects a skill and leaves a partial entry inresolvedSkills.runEmbeddedAttempt→buildTrajectoryRunMetadata→buildSkillsCapture, failing the entire model-fallback chain because every candidate hits the same shared code.Changes
src/logging/diagnostic-support-redaction.ts:redactPathForSupportnow acceptsstring | null | undefinedand returns""for non-string input.src/trajectory/metadata.ts:buildSkillsCapturefilters skill entries without a stringname. If everyresolvedSkillsentry is filtered out (i.e. every resolved entry is partial), falls back toskillsSnapshot.skillsso support bundles don't lose the skill inventory entirely. FinaltoSortedcomparator is null-safe.src/trajectory/metadata.test.ts: regression tests for partial-snapshot tolerance, fallback when all resolved entries are partial, and direct redaction null-input.Security posture unchanged
The symlink-escape rejection in
src/agents/skills/workspace.tsstill refuses to load escaped symlinks and emits the same[skills] Skipping escaped skill path outside its configured rootwarn log. This PR only prevents the downstream support-capture consumer from crashing on the partial entries the rejection path leaves behind.Suggested follow-up (not in this PR)
The producer side —
src/agents/skills/workspace.tsaround thesymlink-escape/bundled-symlink-escapereasons — is what leaves partial entries inresolvedSkills. A cleaner producer-side fix (skip the entry, or emit withavailable: false+ placeholder name) would remove the class of problem. Happy to follow up if preferred.Real behavior proof
TypeError: Cannot read properties of undefined (reading 'startsWith')inrunEmbeddedAttempt→buildTrajectoryRunMetadata→buildSkillsCapturewheneverskillsSnapshot.resolvedSkillscarries a partial entry left behind by the symlink-escape rejection insrc/agents/skills/workspace.ts. Every model in the fallback chain hit the same shared code path, so the entire turn would fail with reason=unknown.pnpm build), reloaded the gateway LaunchDaemon (sudo launchctl bootout system/ai.openclaw.gateway && sudo launchctl bootstrap system /Library/LaunchDaemons/ai.openclaw.gateway.plist), and let live Matrix traffic exercise the symlink-escape rejection path (thehimalayaskill is a symlink that resolves outside~/.openclaw/skills, so it triggers the rejection on every skills-snapshot rebuild).~/.openclaw/logs/gateway.log— log excerpt:~/.openclaw/logs/gateway.err.log— proves the security guard is unchanged and the producer-side path that emits the partial entry is exactly the one this PR catches; live output below:gh run listagainst my fork's recent gateway-driven activity, plus a continuous tail of~/.openclaw/logs/gateway.log, both confirm no newCannot read properties of undefined (reading 'startsWith')lines since the patched dist was deployed; daemon process has been continuously up across many turns of real Matrix traffic.buildSkillsCapturefilters the partial entry instead of crashing, andredactPathForSupportreturns""for the undefined-path case rather than throwing onString.prototype.startsWith. Liveopenclaw gatewayoutput in the same logs above shows continued ws traffic resolving normally.resolvedSkillsshape outside my live deployment, so the regression coverage is the unit tests insrc/trajectory/metadata.test.tsplus the live-gateway observation above. The producer-side cleanup (rejecting the partial entry at the workspace.ts source) was not attempted in this PR — it stays as a follow-up.Test plan
pnpm test src/trajectory/metadata.test.tspasses (6/6, was 3/3)pnpm buildpasses locallypnpm checkpasses locallycodex review --base upstream/mainrun locally — one P2 finding (fallback on fully-filteredresolvedSkills) addressed in-branch; re-ran cleanDegree of testing
Lightly tested — unit tests cover the three failure modes, and the running gateway in my environment has been stable with the patched dist across hours of live traffic on an 11-account Matrix deployment. No integration or e2e coverage added.
🤖 Generated with Claude Code