Problem
handleCancel in plugins/opencode/scripts/opencode-companion.mjs:520-566 resolves the default target via:
const { job, ambiguous } = resolveCancelableJob(state.jobs ?? [], ref);
And resolveCancelableJob in lib/job-control.mjs:138-144 filters only by status === "running" — it does not filter by sessionId. Consequence: if two Claude sessions are running opencode tasks against the same workspace and the user types /opencode:cancel without a job ID in session A, the cancel can target session B's job — silently killing unrelated work.
Contrast with handleStatus at opencode-companion.mjs:483-490, which already filters by sessionId: getClaudeSessionId().
Fix (from upstream diff)
1. Thread sessionId into resolveCancelableJob:
// lib/job-control.mjs
export function resolveCancelableJob(jobs, ref, opts = {}) {
const running = jobs.filter((j) => j.status === "running");
const scoped = opts.sessionId
? running.filter((j) => j.sessionId === opts.sessionId)
: running;
if (!ref) {
return { job: scoped[0] ?? null, ambiguous: scoped.length > 1 };
}
return matchJobReference(scoped, ref);
}
2. Pass the session ID from handleCancel:
async function handleCancel(argv) {
const { positional } = parseArgs(argv, {});
const ref = positional[0];
const workspace = await resolveWorkspace();
const state = loadState(workspace);
const sessionId = getClaudeSessionId();
const { job, ambiguous } = resolveCancelableJob(state.jobs ?? [], ref, { sessionId });
// ... rest unchanged ...
}
3. Error message refinement — when a session ID is set but no running jobs match it:
if (!job) {
if (sessionId) {
console.log("No active OpenCode jobs to cancel for this session.");
} else {
console.log("No active job to cancel.");
}
return;
}
Escape hatch
If a user explicitly passes a job ID prefix (/opencode:cancel task-xyz), the existing prefix match should still work across sessions — they asked for it by name. Only the default (no argument) is session-scoped.
Test plan
- Seed two running jobs with different
sessionId values. Call cancel with no ref, asserting only the current-session one is returned.
- Seed two running jobs in the same session. Assert
ambiguous: true.
- Pass an explicit job ID belonging to another session. Assert the explicit path still finds it.
- Seed one running job in another session, zero in current. Assert error message mentions "for this session".
Upstream reference
openai/codex-plugin-cc#84 (merged 2026-04-08).
Port of openai/codex-plugin-cc#84 (merged)
Problem
handleCancelinplugins/opencode/scripts/opencode-companion.mjs:520-566resolves the default target via:And
resolveCancelableJobinlib/job-control.mjs:138-144filters only bystatus === "running"— it does not filter bysessionId. Consequence: if two Claude sessions are running opencode tasks against the same workspace and the user types/opencode:cancelwithout a job ID in session A, the cancel can target session B's job — silently killing unrelated work.Contrast with
handleStatusatopencode-companion.mjs:483-490, which already filters bysessionId: getClaudeSessionId().Fix (from upstream diff)
1. Thread
sessionIdintoresolveCancelableJob:2. Pass the session ID from
handleCancel:3. Error message refinement — when a session ID is set but no running jobs match it:
Escape hatch
If a user explicitly passes a job ID prefix (
/opencode:cancel task-xyz), the existing prefix match should still work across sessions — they asked for it by name. Only the default (no argument) is session-scoped.Test plan
sessionIdvalues. Call cancel with no ref, asserting only the current-session one is returned.ambiguous: true.Upstream reference
openai/codex-plugin-cc#84 (merged 2026-04-08).