affaan-m/ECC · error
createPlanCanvasServer requires a session store
Error message
createPlanCanvasServer requires a session store
What it means
createPlanCanvasServer() constructs the Plan Canvas HTTP/SSE server and requires a session store — the object that persists session, feedback, and presence state. If the store parameter is missing or falsy the factory throws immediately rather than starting a server with no persistence. It is a constructor-time guard so misconfiguration surfaces at startup, not on first request.
Solutions
- Create and pass a session store: createPlanCanvasServer({ store: new FileSessionStore(dir) }) (use the store implementation your setup documents).
- Check the call site for a typo'd or missing 'store' key in the options object.
- Verify the store factory/instantiation did not fail and return undefined — log its value before the call.
- Follow the library's documented bootstrap (e.g. the canvas command in scripts) instead of hand-rolling the factory call.
Example fix
// before
const server = createPlanCanvasServer({ port: 4111 });
// after
const store = new FileSessionStore(stateDir);
const server = createPlanCanvasServer({ port: 4111, store }); Defensive patterns
Strategy: validation
Validate before calling
if (!store) {
throw new Error('Plan Canvas: create the session store before createPlanCanvasServer.');
}
const server = createPlanCanvasServer({ ...opts, store }); Type guard
function hasSessionStore(opts) {
return Boolean(opts && opts.store && typeof opts.store === 'object');
} Try / catch
try {
const server = createPlanCanvasServer(opts);
} catch (e) {
if (e.message === 'createPlanCanvasServer requires a session store') {
console.error('Options object is missing `store` — construct the store (e.g. new FileSessionStore(dir)) and pass it.');
} else throw e;
} Prevention
- Instantiate the session store first and pass it explicitly in the options object.
- Check that the store factory did not fail and silently return undefined/null.
- Keep server bootstrap in one reviewed launcher script rather than ad-hoc invocations.
- Add a startup smoke test that constructs the server with a temp store.
When it happens
Trigger: Calling createPlanCanvasServer() with no arguments; passing an options object without a store key ({ host, port, ... } but no store); passing store: null/undefined after a failed store initialization (e.g. store factory returned null).
Common situations: Wiring up the canvas server in a custom launcher and forgetting to create the store (e.g. new FileSessionStore(...) or equivalent) first; destructuring config where the store field is absent; a store module failing to load and silently yielding undefined.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- replay.no_store
- a claim token is required
- An executable is required after --.
- An install component ID is required
- An install config path is required
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/8f28a8f04a691379.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/plan-canvas/server.js:126
"default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-src 'self'";
}
res.writeHead(statusCode, headers);
res.end(html);
}
function createPlanCanvasServer({
store,
host = DEFAULT_HOST,
version = '0.0.0',
idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS,
heartbeatMs = 15000,
thinkingStaleMs = DEFAULT_THINKING_STALE_MS,
typingExpiryMs = DEFAULT_TYPING_EXPIRY_MS,
presenceSweepMs = DEFAULT_PRESENCE_SWEEP_MS,
onIdleShutdown = null,
log = () => {}
} = {}) {
if (!store) throw new Error('createPlanCanvasServer requires a session store');
const allowedHostnames = buildAllowedHostnames(host);
const wake = new EventEmitter();
wake.setMaxListeners(0);
const sseClients = new Map(); // key -> Set<res>
const awaitCounts = new Map(); // key -> active long-poll count
const workingKeys = new Map(); // key -> ms timestamp the agent took feedback
const typingKeys = new Map(); // key -> ms timestamp the agent signalled composing
const watchers = new Map(); // key -> fs.FSWatcher
const lastPresence = new Map(); // key -> last broadcast state, for sweep diffing
let idleTimer = null;
let presenceSweep = null;
let closed = false;
// --- presence + SSE ---------------------------------------------------
/**
* Presence never claims more than the server actually knows:View on GitHub (pinned to 8321021c54)