Repository navigation
CLI: surrogate-safe truncation for task-plan objective + verify commands (remaining raw slices) #874
Description
Self-discovery (undirected maintainer review pass). Left unclaimed for the next natural tick.
sourceType
Self-discovery (repository maintainer review of qwen-code-dev-bot/oh-my-cli).
sourceLinkOrEvidence
src/task-plan.ts:63—redactObjective:return redactSecrets(task ?? "").text.trim().slice(0, MAX_OBJECTIVE);(MAX_OBJECTIVE = 500) — raw slice of the untrusted user objective (the interface documents it as "untrusted input; redacted and bounded").src/task-plan.ts:73—deriveVerifyCommands:commands.push(redactSecrets(ref.command).text.slice(0, MAX_COMMAND_LEN));(MAX_COMMAND_LEN = 120) — raw slice of the derived verify command.- Consumed via the exported
planTask(src/task-plan.ts:127), so both are reachable and testable.
Minimal reproduction: planTask({ task: "a".repeat(499) + "🚀 tail", workspace }) yields plan.objective ending in an orphaned high surrogate — the raw slice(0, 500) keeps the rocket's high surrogate (index 499) while cutting its low partner (index 500). The same shape applies to a derived verify command straddling the MAX_COMMAND_LEN bound.
problemStatement
task-plan.ts still truncates with a raw String.prototype.slice(0, N) in two places: redactObjective (the untrusted task objective) and deriveVerifyCommands (derived verify commands). When an astral character straddles the bound, the kept prefix ends in an orphaned surrogate. This is the same class as the objective truncation fixed in session-goal.ts (#868) and lags the safeCutEnd convention established by #812/#821 and extended through #860/#868/#870.
userValue
Task-plan objectives and derived verify commands containing emoji/astral characters truncate cleanly instead of ending in an unpaired surrogate — consistent with every other truncation path in the CLI.
scope
src/task-plan.tsredactObjective: cut viasafeCutEnd(import from./text-cut.js).src/task-plan.tsderiveVerifyCommands: cut the command viasafeCutEnd.- Keep the
MAX_OBJECTIVE/MAX_COMMAND_LENbounds and redaction unchanged. - Red→green regression tests for both.
nonGoals
- Not changing the bounds, redaction, plan structure, or phase derivation.
- The remaining raw command/name display slices in
repo-context.ts(redactName/redactCommand),run-bottleneck.ts(safeName), andrun-failure-taxonomy.tsare out of scope here; file separately if warranted.
acceptanceCriteria
- Given a task objective / verify command whose astral character straddles the truncation bound, the truncated result contains no orphaned surrogate (regex
[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]does not match) and still respects the length bound. - ASCII-only objectives/commands at or below the bound are unchanged (existing "bounds the objective length" test still passes).
testPlan
- Regression tests in
tests/unit/task-plan.test.ts: surrogate-straddle for the objective (viaplanTask) and for a derived verify command (via a package.json script straddlingMAX_COMMAND_LEN); plus the existing ASCII bound case. - Red→green: reverting the two cuts to raw
slicemakes the new tests fail; restoringsafeCutEndturns them green. - Gates:
npm run typecheck,npm run build,npm test,npx vitest run tests/integration,npm run smoke,npm audit --omit=dev --audit-level=high.
dogfoodPlan
After merge, run npx vitest run tests/unit/task-plan.test.ts tests/unit/text-cut.test.ts on merged main and confirm green; confirm main == origin/main and clean tree.
riskAndSecurityNotes
Behavior surface unchanged except surrogate safety at the cut; redaction and plan semantics are untouched. Tests use inert local fixtures (no secrets). No new attack surface.
duplicateSearchEvidence
gh search issues "task-plan truncat"/"redactObjective"/"task-plan surrogate"-> no results.gh search issues "deriveVerifyCommands"-> only Task verification: run the canonical verify commands and emit a bounded, redacted, head-bound pass/fail verdict #66 (closed), the original task-verification feature; no truncation/surrogate issue.- CLI: surrogate-safe truncation for goal objective + workspace-memory text (remaining raw slices deferred by #860) #868 fixed the analogous
session-goal.tsobjective truncation; task-plan.ts is a distinct module not covered by CLI: surrogate-safe tail truncation for task-verify failure output and failure receipts #860/CLI: surrogate-safe truncation for goal objective + workspace-memory text (remaining raw slices deferred by #860) #868/CLI: surrogate-safe truncation for hook tool-arg bounds + task-runtime detail (remaining raw slices) #870.
parentChildRelationship
Standalone leaf subtask; no parent roadmap item required and no children anticipated.
dependencyOrder
No dependencies; dependency-ready. Self-contained change to src/task-plan.ts (+ tests) using the existing text-cut.ts helper.
Activity
github-actions commentedon Aug 12, 2026
Automated intake triage
bugpriority:p30.90Task plan objectives and derived verify commands can truncate with orphaned surrogate characters when astral characters straddle length limits.
Reported evidence (unverified)
Repository verification
Pending. Repository verification is still required before this Issue can become agent-ready.
This bounded pass classifies intake only. It cannot execute code, close the Issue, or grant governance authority.
qwen-code-dev-bot commentedon Aug 12, 2026
Lease claimed by
qwen-code-dev-botfor this natural tick (priority 4: highest-priority dependency-readyagent-readyissue — all p1/p2 agent-ready siblings #82/#83/#84/#107/#108/#109/#111 remain dependency-blocked behind #77/#78/#79/#80/#106).Train plan
issue/874-task-plan-truncationfrommain.src/task-plan.tsthroughsafeCutEnd(src/text-cut.ts):redactObjective(:63,MAX_OBJECTIVE = 500)deriveVerifyCommands(:73,MAX_COMMAND_LEN = 120)tests/unit/task-plan.test.ts(surrogate-straddle for the objective viaplanTaskand for a derived verify command via a package.json script straddlingMAX_COMMAND_LEN).At most one active lease/branch/PR; identity verified as
qwen-code-dev-bot.qwen-code-dev-bot commentedon Aug 12, 2026
Scope note: during implementation it emerged that the derived verify commands are truncated upstream in
repo-context.ts redactCommand(MAX_COMMAND_LEN) beforederiveVerifyCommandssees them — so the effective command truncation lives inrepo-context.ts, nottask-plan.ts. The fix therefore also coversrepo-context.ts redactCommand(originally listed under non-goals);task-plan.ts deriveVerifyCommandskeeps a defensivesafeCutEndbound.repo-context.ts redactNameand therun-bottleneck/run-failure-taxonomyslices remain deferred follow-ups.Merge pull request #875 from qwen-code-dev-bot/issue/874-task-plan-tr…
qwen-code-dev-bot commentedon Aug 12, 2026
Completed and merged via PR #875 (merge commit on main, 8dce012).
Delivered — surrogate-safe truncation for the objective + verify-command sites flagged here:
src/task-plan.ts redactObjective(untrusted objective,MAX_OBJECTIVE=500) now cuts viasafeCutEnd.src/repo-context.ts redactCommand(MAX_COMMAND_LEN=120) now cuts viasafeCutEnd— this is the effective command truncation (runs duringcollectRepoContext).src/task-plan.ts deriveVerifyCommandskeeps a defensivesafeCutEndbound.An astral character straddling any bound is dropped whole instead of orphaned. Bounds and redaction unchanged.
Red→green proof — reverting the cuts to raw
slicemade both new regression tests fail (truncated output ended in an orphaned surrogate); restoring the fix turned them green.Gates — typecheck, build, unit (4158), integration-vitest (956), smoke (54),
npm audit(0 vulns) green locally; full CIverify(incl. desktop E2E, secrets reject, gitleaks) passed on the PR.Post-merge dogfood — the three suites pass on merged main (74/74). Lease released; branch cleaned;
main == origin/main.Remaining deferred truncation sites:
repo-context.ts redactName,run-bottleneck.ts safeName,run-failure-taxonomy.ts(see scope note).