{"record":{"id":"819dee74b0b2ddde","repo":"affaan-m/ECC","slug":"typing-failed-http-res-statuscode","errorCode":null,"errorMessage":"typing failed (HTTP ${res.statusCode})","messagePattern":"typing failed \\(HTTP (.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"scripts/plan-canvas.js","lineNumber":307,"sourceCode":"      : 'Address the feedback, then run `ecc-plan-canvas await <file> --reply \"<what you changed>\"` to answer in the canvas and keep listening.';\n  } else if (result.status === 'ended') {\n    result.next_step =\n      result.endedBy === 'user'\n        ? 'The user ended this review. Stop polling and deliver any remaining updates in chat; do not reopen uninvited.'\n        : 'Session ended. Stop polling.';\n  }\n  return result;\n}\n\n// Show the human an activity indicator in the canvas chat. Cheap and\n// fire-and-forget: a failed signal must never derail the actual work.\nasync function cmdTyping(file, args, { port }) {\n  if (!file) throw new Error('typing requires a file path');\n  const state = valueAfter(args, '--state') || 'typing';\n  if (!(await healthCheck(port))) return { status: 'no-server' };\n  const key = sessionKeyFor(canonicalizeArtifactPath(file));\n  const res = await request(port, 'POST', `/api/session/${key}/typing`, { state });\n  if (res.statusCode !== 200) throw new Error(res.body.error || `typing failed (HTTP ${res.statusCode})`);\n  return { status: 'ok', state, presence: res.body.presence };\n}\n\n// Report feedback the human sent that no agent has picked up yet. Reads state\n// directly so it answers even when the server has idled out.\nfunction cmdPending({ stateDir }) {\n  const store = createSessionStore({ stateDir });\n  const waiting = store\n    .list()\n    .filter(session => session.status !== 'ended' && session.pending > 0)\n    .map(session => ({ file: session.file, pending: session.pending, updatedAt: session.updatedAt }));\n  return {\n    status: waiting.length ? 'pending' : 'clear',\n    sessions: waiting,\n    next_step: waiting.length\n      ? 'Run `ecc-plan-canvas await <file>` for each file above to receive the messages.'\n      : 'No canvas feedback is waiting.'\n  };","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/plan-canvas.js#L289-L325","documentation":"Thrown by cmdTyping when the canvas server responds to the typing POST with a non-200 status and the response body carried no `error` field. It is a fallback message that surfaces the raw HTTP status so the caller can tell a transport/session problem apart from a missing file. The preferred server-supplied `res.body.error` is used when present.","triggerScenarios":"The session key has no active session server-side (404); the server restarted and lost in-memory state; the typing endpoint rejected the `state` value; network/proxy returned a 5xx; port collision reaching a different service.","commonSituations":"Canvas server idled out and was reaped but the caller kept polling; `open` never ran for that file so no session row exists; server version mismatch where the typing route was renamed; another process grabbed the same port.","solutions":["Run `node scripts/plan-canvas.js open <file>` to (re)create the session, then retry typing.","Check server health: the cmdTyping path calls healthCheck first — if that passed but typing still 4xx'd, the session key is stale; re-open.","Confirm the port in the state dir matches the running server (serverInfoPath) and no other service hijacked it.","Because typing is fire-and-forget, log the status but do not fail the parent workflow."],"exampleFix":"// before\nconst res = await request(port, 'POST', `/api/session/${key}/typing`, { state });\nif (res.statusCode !== 200) throw new Error(res.body.error || `typing failed (HTTP ${res.statusCode})`);\n// after — tolerate stale-session so presence never breaks the agent\nconst res = await request(port, 'POST', `/api/session/${key}/typing`, { state }).catch(() => null);\nif (!res || res.statusCode !== 200) {\n  console.error(`[plan-canvas] typing signal skipped (HTTP ${res ? res.statusCode : 'no-response'})`);\n  return { status: 'skipped' };\n}","handlingStrategy":"try-catch","validationCode":"// Health + session existence check before typing\nasync function sessionExists(port, key) {\n  const res = await request(port, 'GET', '/api/sessions');\n  return Array.isArray(res.body) && res.body.some(s => s.key === key);\n}","typeGuard":"function isTypingOk(res) {\n  return res && typeof res === 'object' && res.statusCode === 200;\n}","tryCatchPattern":"let presence = null;\ntry {\n  const res = await request(port, 'POST', `/api/session/${key}/typing`, { state });\n  if (res.statusCode !== 200) throw new Error(res.body.error || `typing failed (HTTP ${res.statusCode})`);\n  presence = res.body.presence;\n} catch (err) {\n  console.error(`[plan-canvas] typing skipped: ${err.message}`);\n  // non-fatal: the real work continues\n}","preventionTips":["Re-open the session if it may have idled out before sending presence.","Confirm the running server is the plan-canvas server (check serverInfoPath), not a port-hijacker.","Never let a typing failure fail the agent turn — wrap it."],"tags":["http","plan-canvas","presence","server-state"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}