Skip to content

fix(note): prevent clack from re-breaking copy-sensitive tokens#94738

Closed
xzh-icenter wants to merge 2 commits into
openclaw:mainfrom
xzh-icenter:fix/issue-94730
Closed

fix(note): prevent clack from re-breaking copy-sensitive tokens#94738
xzh-icenter wants to merge 2 commits into
openclaw:mainfrom
xzh-icenter:fix/issue-94730

Conversation

@xzh-icenter

Copy link
Copy Markdown
Contributor

Fixes #94730

Summary

  • Problem: openclaw doctor mangles long file paths inside note(...) boxes. wrapNoteMessage correctly preserves copy-sensitive tokens as unbroken lines, but clack's note() applies its own internal wrap after our format callback, re-breaking the line at the terminal width.
  • Why now: This affects every user running openclaw doctor in a terminal narrower than the longest path/URL in the output. The mangled path cannot be copy-pasted, defeating the purpose of surfacing it.
  • What changed: Use a wide virtual stream (columns = max(80, longestLine + 12)) in the note() function so clack's internal wrap never triggers, letting wrapNoteMessage's output pass through to the terminal intact.
  • What did NOT change: No changes to wrapNoteMessage, wrapLine, isCopySensitiveToken, or splitLongWord. The fix is in the note() function only.
  • Outcome: Long paths and URLs in note() boxes are now displayed as a single line that can be copy-pasted, regardless of terminal width.
  • Reviewer focus: The 5-line change in packages/terminal-core/src/note.ts lines 209-215.

Verification

# Reproduce the bug: clack re-breaks copy-sensitive tokens
node -e "
const { note } = require('@clack/prompts');
const longPath = '~/.openclaw/agents/main/sessions/9c2acae5-841f-4aea-936b-fdb513b60202.jsonl.lock';
const message = 'Session lock: ' + longPath;
const output = [];
note(message, 'Test', {
  output: { columns: 80, write: (c) => { output.push(c); return true; }, isTTY: true },
  format: (line) => line,
});
const result = output.join('');
console.log('Path intact:', result.includes(longPath));
"

Real behavior proof

Behavior addressed: Long file paths in openclaw doctor note boxes are split across multiple lines, making them impossible to copy-paste.

Environment tested: Linux 6.18.33.1-microsoft-standard-WSL2 (x64), Node.js v24.16.0.

Steps run after the patch:

cd /mnt/g/code/openclaw-worktrees/issue-94730

# Test 1: Old behavior (80 col) - path gets broken
node -e "
const { note } = require('@clack/prompts');
const longPath = '~/.openclaw/agents/main/sessions/9c2acae5-841f-4aea-936b-fdb513b60202.jsonl.lock';
const message = 'Session lock: ' + longPath;
const output1 = [];
note(message, 'Old (80 col)', {
  output: { columns: 80, write: (c) => { output1.push(c); return true; }, isTTY: true },
  format: (line) => line,
});
const output2 = [];
const wideColumns = Math.max(80, message.length + 12);
note(message, 'New (wide)', {
  output: { columns: wideColumns, write: (c) => { output2.push(c); return true; }, isTTY: true },
  format: (line) => line,
});
console.log('Old (80 col) - path intact:', output1.join('').includes(longPath));
console.log('New (wide)   - path intact:', output2.join('').includes(longPath));
"

Evidence after fix (console output):

Old (80 col) - path intact: false
New (wide)   - path intact: true

Observed result: With the old 80-column stream, clack's internal wrap breaks the path at character 74. With the wide virtual stream, the path remains intact as a single line.

Not tested: Live openclaw doctor run (requires full gateway setup). The fix only changes the virtual stream width passed to clack's note().

wrapNoteMessage correctly preserves copy-sensitive tokens (paths, URLs)
as unbroken lines, but clack's note() applies its own internal wrap
after our format callback, re-breaking the line at the terminal width.

Use a wide virtual stream (columns = max(80, longestLine + 12)) so
clack's internal wrap never triggers, letting wrapNoteMessage's output
pass through to the terminal intact.

Fixes openclaw#94730
@openclaw-barnacle openclaw-barnacle Bot added size: XS proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 19, 2026
@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed June 18, 2026, 9:49 PM ET / 01:49 UTC.

Summary
The PR widens the virtual output stream passed to Clack's note() and adds terminal-core regression coverage for copy-sensitive path and URL preservation.

PR surface: Source +5, Tests +64. Total +69 across 2 files.

Reproducibility: yes. for source-level reproduction: current main routes session-lock paths through the shared note() helper, and Clack 1.4.0 hard-wraps final note content from getColumns(output) - 6. I did not run a live openclaw doctor scenario in this read-only review.

Review metrics: 1 noteworthy metric.

  • Typed test check: 1 failed lane. check-test-types fails on the current head, matching the unsupported call shape in the new test.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #94730
Summary: This PR is a candidate fix for the canonical doctor note wrapping bug; several sibling PRs target the same root cause, with only this PR and #94744 still open.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🐚 platinum hermit
Patch quality: 🧂 unranked krab
Result: blocked by patch quality or review findings.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Repair the rendered-output regression test so it uses a supported seam, such as a mocked @clack/prompts call or a narrow output-column helper.
  • [P2] Rerun node scripts/run-vitest.mjs packages/terminal-core/src/note.test.ts packages/terminal-core/src/table.test.ts and the test-types lane after the fix.

Risk before merge

  • [P1] The added regression test currently cannot validate the production path because it calls a two-argument OpenClaw wrapper as though it accepted Clack-style options.
  • [P1] A parallel open candidate fix exists at fix: preserve copy-safe doctor paths in note boxes #94744, so maintainers should choose one canonical branch and avoid landing duplicate terminal-note variants.

Maintainer options:

  1. Decide the mitigation before merge
    Land one canonical terminal-core fix that preserves copy-sensitive tokens through the final Clack-rendered note box and includes passing rendered-output regression coverage.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] A narrow automated repair can fix the unsupported test call while preserving the production width change; the fork allows maintainer edits.

Security
Cleared: The diff only changes terminal note rendering width calculation and tests; it adds no dependency, workflow, secret, network, package, or code-execution surface.

Review findings

  • [P1] Use a supported hook for the rendered note test — packages/terminal-core/src/note.test.ts:55-58
Review details

Best possible solution:

Land one canonical terminal-core fix that preserves copy-sensitive tokens through the final Clack-rendered note box and includes passing rendered-output regression coverage.

Do we have a high-confidence way to reproduce the issue?

Yes for source-level reproduction: current main routes session-lock paths through the shared note() helper, and Clack 1.4.0 hard-wraps final note content from getColumns(output) - 6. I did not run a live openclaw doctor scenario in this read-only review.

Is this the best way to solve the issue?

No as submitted: the shared terminal-core note() helper is the right layer, but the new regression test uses an unsupported API and currently blocks the typed test lane. The safer path is to keep the production fix shape and repair coverage by mocking Clack or extracting a narrow output-column helper.

Full review comments:

  • [P1] Use a supported hook for the rendered note test — packages/terminal-core/src/note.test.ts:55-58
    note() is still declared as note(message, title?) and always writes through process.stdout, so this third Clack-style options argument is rejected by the typed test lane and ignored at runtime. The mock output array therefore never captures the rendered note, and this new regression test cannot pass or protect the production path until it mocks @clack/prompts or tests an extracted output-column helper/rendering seam.
    Confidence: 0.96

Overall correctness: patch is incorrect
Overall confidence: 0.91

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 10f0588ee39b.

Label changes

Label changes:

  • add rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🐚 platinum hermit and patch quality is 🧂 unranked krab.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The PR body includes copied terminal output comparing the old 80-column Clack render with the widened output path after the patch and shows the long path remains intact; it does not include a full openclaw doctor run.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🧂 unranked krab, so this older rating label is no longer current.
  • remove status: 👀 ready for maintainer look: Current PR status label is status: ⏳ waiting on author.

Label justifications:

  • P2: This is a normal-priority CLI doctor rendering bug with limited terminal-output blast radius, but the PR currently has a blocking test/type defect.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🐚 platinum hermit and patch quality is 🧂 unranked krab.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The PR body includes copied terminal output comparing the old 80-column Clack render with the widened output path after the patch and shows the long path remains intact; it does not include a full openclaw doctor run.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied terminal output comparing the old 80-column Clack render with the widened output path after the patch and shows the long path remains intact; it does not include a full openclaw doctor run.
Evidence reviewed

PR surface:

Source +5, Tests +64. Total +69 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 7 2 +5
Tests 1 64 0 +64
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 71 2 +69

Acceptance criteria:

  • [P1] node scripts/run-vitest.mjs packages/terminal-core/src/note.test.ts packages/terminal-core/src/table.test.ts.
  • [P1] check-test-types lane or equivalent pnpm check:test-types in the repo-approved environment.

What I checked:

  • Broken PR test call: The new regression test calls OpenClaw's note(message, title, options) with a mock output stream, but the PR head keeps note() as a two-argument wrapper, so the third argument is rejected by test types and ignored at runtime. (packages/terminal-core/src/note.test.ts:55, 6be16c7985f4)
  • CI confirms the typed test surface fails: The PR head has check-test-types completed with conclusion FAILURE while check-prod-types passed, consistent with the unsupported test-only call shape. (6be16c7985f4)
  • Current main still has the reported behavior: Current main still wraps once with wrapNoteMessage(...) and then passes the result to Clack with createNoteOutput(columns), so the linked issue is not already fixed on main. (packages/terminal-core/src/note.ts:210, 10f0588ee39b)
  • Affected doctor caller uses the shared note helper: Session lock diagnostics format the lock path into note lines and emit them through note(lines.join("\n"), "Session locks"), so the shared terminal-core helper is the correct layer for this fix. (src/commands/doctor-session-locks.ts:97, 10f0588ee39b)
  • Dependency contract checked: At @clack/[email protected], Clack wraps note content with wrapAnsi(message, getColumns(output) - 6, { hard: true, trim: false }), so virtual output columns directly control the second wrapping pass. (upstream:bombshell-dev/clack/packages/prompts/src/note.ts:25, f4818cd9aa2a)
  • Latest release still has the vulnerable shape: The latest release tag v2026.6.8 still calls clackNote(wrapNoteMessage(...)) with the normal terminal columns, so this bug has not shipped fixed yet. (packages/terminal-core/src/note.ts:210, 844f405ac1be)

Likely related people:

  • steipete: GitHub file history shows this account moved terminal note code into packages/terminal-core and previously refactored shared terminal note wrapping. (role: terminal note history contributor; confidence: high; commits: de1dfab03ef0, f7f5c24786ad, 4df95d3c3fd6; files: packages/terminal-core/src/note.ts, packages/terminal-core/src/table.test.ts, src/terminal/note.ts)
  • vignesh07: History for the older terminal note path includes prior work preserving copy-sensitive token wrapping, which is the invariant this PR extends through Clack rendering. (role: adjacent copy-sensitive wrapping contributor; confidence: medium; commits: 69418cca2091; files: src/terminal/note.ts, src/tui/tui-formatters.ts, src/tui/tui-formatters.test.ts)
  • Grynn: The session lock doctor check that exposes this note-rendering bug was added in a prior merged session-lock fix. (role: doctor session-lock feature contributor; confidence: medium; commits: e91a5b021648; files: src/commands/doctor-session-locks.ts)
  • jbetala7: Recent terminal-core table/ANSI tests touched adjacent wrapping and width behavior in the same test file family. (role: recent adjacent terminal-core test contributor; confidence: medium; commits: 29dd7847fd96; files: packages/terminal-core/src/table.test.ts, packages/terminal-core/src/ansi.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. labels Jun 19, 2026
Add focused tests for wrapNoteMessage and note() that verify
copy-sensitive tokens (paths, URLs, Windows paths) are preserved
as unbroken lines, guarding against future regressions.

Refs openclaw#94730
@xzh-icenter

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 19, 2026
@xzh-icenter

Copy link
Copy Markdown
Contributor Author

Closing this PR due to CI failure from the test commit. Will reopen with just the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: S status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

doctor: copy-sensitive paths get re-wrapped by clack note box, splitting file extensions

1 participant