Skip to content

fix: claude-lane/task_1780281408931_2vxzzbu7l-20260602125813 - #30

Closed
nehraa wants to merge 1 commit into
mainfrom
claude-lane/task_1780281408931_2vxzzbu7l-20260602125813
Closed

fix: claude-lane/task_1780281408931_2vxzzbu7l-20260602125813#30
nehraa wants to merge 1 commit into
mainfrom
claude-lane/task_1780281408931_2vxzzbu7l-20260602125813

Conversation

@nehraa

@nehraa nehraa commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Automated DevPulse recovery — see commit history.


Recovered by recover_unprd_tasks.py

Copilot AI review requested due to automatic review settings June 3, 2026 11:43
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@nehraa has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 57 minutes and 17 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 545bbf9e-ccf0-4635-b673-ecc1e482e8ad

📥 Commits

Reviewing files that changed from the base of the PR and between a896899 and 5705484.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • src/lib/server/terminal-sessions.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude-lane/task_1780281408931_2vxzzbu7l-20260602125813

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Fix terminal sessions memory leak with idle timeout and cleanup

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Implement session memory leak prevention with idle timeout
• Add automatic cleanup of expired terminal sessions
• Enforce maximum session limit to prevent resource exhaustion
• Purge idle sessions on list and get operations
Diagram
flowchart LR
  A["Terminal Session Created"] --> B["Check Max Sessions Limit"]
  B --> C["Session Active"]
  C --> D["Idle Timeout Check"]
  D --> E["Purge Expired Sessions"]
  E --> F["Session Cleanup Complete"]
  G["List/Get Sessions"] --> E

Loading

Grey Divider

File Changes

1. src/lib/server/terminal-sessions.ts 🐞 Bug fix +43/-2

Add idle timeout and session cleanup logic

• Added three constants for session management: IDLE_TIMEOUT_MS (10 minutes),
 EXPIRED_OUTPUT_GRACE_MS (60 seconds), and MAX_SESSIONS (50)
• Implemented isIdleExpired() function to check if a session has exceeded idle timeout based on
 its status
• Implemented purgeIdleSessions() function to remove expired sessions and terminate running child
 processes
• Added purgeIdleSessions() calls in listTerminalSessions(), getTerminalSession(), and
 createTerminalSession() functions
• Added session limit validation in createTerminalSession() to throw error when max sessions
 reached

src/lib/server/terminal-sessions.ts


2. pnpm-lock.yaml Dependencies +259/-0

Update lock file with new workspace packages

• Added new package entries for codeflow-canvas, codeflow-dtwin, and codeflow-evolution
 workspaces
• Added dependency entries for @abhinav2203/codeflow-core@1.1.6 and
 @abhinav2203/codeflow-execution@0.1.0
• Added new package versions including monaco-editor@0.52.2, dotenv@16.6.1, react@18.3.1,
 react-dom@18.3.1
• Added snapshot entries for new package combinations with React 18.3.1 and related dependencies

pnpm-lock.yaml


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Jun 3, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0)

Grey Divider


Action required

1. Idle timeout not guaranteed 🐞 Bug ☼ Reliability
Description
Idle session eviction only happens when
listTerminalSessions/getTerminalSession/createTerminalSession are called, so abandoned sessions can
keep their shell processes alive indefinitely if no further API requests occur. This defeats the
intent of IDLE_TIMEOUT_MS and can leak OS processes and memory until the server receives another
terminal-related request.
Code

src/lib/server/terminal-sessions.ts[R156-167]

Evidence
The only invocations of purgeIdleSessions are in list/get/create, and there is no background
scheduler; therefore no calls means no cleanup.

src/lib/server/terminal-sessions.ts[54-179]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`purgeIdleSessions()` is only invoked opportunistically (on list/get/create). If a client disconnects and stops calling these endpoints, idle sessions will never be purged and the underlying shell processes can continue running.

### Issue Context
The code defines `IDLE_TIMEOUT_MS`/`EXPIRED_OUTPUT_GRACE_MS`, but there is no background timer or per-session timer to enforce expiry.

### Fix Focus Areas
- src/lib/server/terminal-sessions.ts[34-179]

### Suggested fix
- Add a background `setInterval(() => purgeIdleSessions(), N)` (and `.unref()` if appropriate) so expiry is enforced even without inbound requests.
- Alternatively (often better), attach a per-session timer on creation that is reset whenever `lastActivityAt` changes; on timer fire, kill and cleanup that specific session.
- Add a small vitest to simulate time passing and verify sessions are purged without calling list/get/create.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Orphaned terminal processes 🐞 Bug ☼ Reliability
Description
purgeIdleSessions() sends SIGTERM and immediately deletes the session from the map; if the child
ignores SIGTERM or takes time to exit, it can keep running with no remaining handle for
escalation/cleanup. This can leak long-lived processes and their event listeners/output buffers
beyond the session’s lifecycle.
Code

src/lib/server/terminal-sessions.ts[R54-65]

Evidence
The implementation kills with SIGTERM and deletes the session unconditionally without checking for
process termination or escalating to SIGKILL.

src/lib/server/terminal-sessions.ts[54-66]
src/lib/server/terminal-sessions.ts[264-276]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Expired sessions are removed from `sessions` immediately after sending SIGTERM. If the process does not exit promptly, it becomes unmanaged and may continue consuming resources.

### Issue Context
`purgeIdleSessions()` and `closeTerminalSession()` both call `child.kill('SIGTERM')` and then delete the session right away.

### Fix Focus Areas
- src/lib/server/terminal-sessions.ts[54-66]
- src/lib/server/terminal-sessions.ts[264-276]

### Suggested fix
- When expiring/closing a running session:
 - Mark it as closing (optional), send SIGTERM.
 - Wait for the child's `close` event (or a short timeout).
 - If it does not close within the timeout, send SIGKILL.
 - Only then delete from the map.
- Ensure listeners are removed on cleanup to avoid retaining session objects longer than necessary.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Session cap returns 500 🐞 Bug ≡ Correctness
Description
When MAX_SESSIONS is reached, createTerminalSession throws a generic Error that the API route
classifies as a 500, misreporting a resource-limit/client condition as an internal server error.
This breaks client handling and pollutes server error metrics/logs with expected limit failures.
Code

src/lib/server/terminal-sessions.ts[R173-178]

Evidence
The new session-limit throw is in terminal-sessions.ts, and the route handler converts non-Zod
errors into a 500 response.

src/lib/server/terminal-sessions.ts[169-179]
src/app/api/terminal/sessions/route.ts[20-41]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`createTerminalSession()` throws on session limit, but `POST /api/terminal/sessions` returns HTTP 500 for non-Zod errors, turning an expected limit into an internal error.

### Issue Context
- Session limit is enforced in `createTerminalSession()`.
- The API route uses `error instanceof z.ZodError ? 400 : 500`.

### Fix Focus Areas
- src/lib/server/terminal-sessions.ts[169-179]
- src/app/api/terminal/sessions/route.ts[20-41]

### Suggested fix
- Introduce a typed error (e.g., `TerminalSessionLimitError`) or error code field.
- In the route handler, map that specific error to `429 Too Many Requests` (or `409 Conflict`), and keep 500 for true server faults.
- Add a test asserting the correct status code when MAX_SESSIONS is reached.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Library bundles React copy 🐞 Bug ⚙ Maintainability
Description
The new codeflow-canvas workspace resolves React 18 while the root app uses React 19, and
codeflow-canvas declares react/react-dom in both dependencies and peerDependencies, which commonly
causes duplicate-React installs for consumers. This configuration makes runtime "Invalid hook
call"-class failures likely when the library is consumed by a React 19 app.
Code

pnpm-lock.yaml[R171-193]

Evidence
pnpm-lock shows codeflow-canvas resolves React 18.3.1, while the root app depends on React 19;
codeflow-canvas package.json also declares React 18 in dependencies/peers, meaning it will
ship/install React rather than relying solely on the consumer’s React.

pnpm-lock.yaml[171-216]
pnpm-lock.yaml[3964-3970]
package.json[20-38]
packages/codeflow-canvas/package.json[29-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`@abhinav2203/codeflow-canvas` currently installs its own React (18.x) while the repository root uses React 19.x. Publishing a component library with React in `dependencies` often leads to two React copies in consumer bundles and hook/runtime failures.

### Issue Context
- Root depends on React 19.
- codeflow-canvas depends on React 18 in `dependencies` while also listing it as a peer.
- pnpm-lock now contains both `react@18.3.1` and `react@19.2.5` resolutions.

### Fix Focus Areas
- packages/codeflow-canvas/package.json[29-51]
- package.json[20-38]
- pnpm-lock.yaml[171-216]

### Suggested fix
- In `packages/codeflow-canvas/package.json`:
 - Remove `react` and `react-dom` from `dependencies`.
 - Keep them in `peerDependencies` (and add them to `devDependencies` for local build/test).
 - Consider widening peers to support React 19 if intended (e.g., `^18.0.0 || ^19.0.0`).
- Re-run pnpm to update the lockfile accordingly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces idle session purging and a maximum session limit of 50 to the terminal session manager. It adds logic to check for idle sessions (10-minute timeout for running sessions and a 1-minute grace period for exited sessions) and purges them during list, get, and create operations. A critical bug was identified in the purging logic where running sessions are immediately deleted from the sessions map upon receiving a SIGTERM signal. This bypasses the intended 60-second grace period for users to view final output and can leak orphaned processes if they do not terminate instantly. The reviewer provided a code suggestion to update the last activity timestamp instead of immediate deletion, allowing the process close event to handle the transition to exited status.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +54 to +66
const purgeIdleSessions = (now: number = Date.now()): void => {
for (const [id, session] of sessions) {
if (!isIdleExpired(session, now)) {
continue;
}

if (session.status === "running") {
session.child.kill("SIGTERM");
}

sessions.delete(id);
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Bug: Immediate Deletion of Running Sessions Bypasses Grace Period & Can Leak Processes

When a running session is determined to be idle, purgeIdleSessions calls session.child.kill("SIGTERM") and immediately deletes the session from the sessions map. This introduces two major issues:

  1. Resource Leak / Orphaned Processes: If the child process does not terminate instantly (or ignores SIGTERM), we lose the reference to it. It will continue running in the background as an orphaned process and can never be cleaned up or killed during a shutdown (e.g., in shutdownAllTerminalSessions).
  2. Bypassed Grace Period: The user will not be able to see the termination notice or final output of the idle-purged session because it is deleted from the map immediately, completely bypassing the 60-second grace period (EXPIRED_OUTPUT_GRACE_MS) that exited sessions are supposed to have.

Solution

Instead of deleting the session immediately when it is running, we should send SIGTERM and update its lastActivityAt to the current time. This prevents it from being killed again on subsequent checks. Once the process actually exits, the close event will transition its status to "exited" and update lastActivityAt again, allowing the session to remain visible to the user for the 60-second grace period before being deleted in a future purge.

Suggested change
const purgeIdleSessions = (now: number = Date.now()): void => {
for (const [id, session] of sessions) {
if (!isIdleExpired(session, now)) {
continue;
}
if (session.status === "running") {
session.child.kill("SIGTERM");
}
sessions.delete(id);
}
};
const purgeIdleSessions = (now: number = Date.now()): void => {
for (const [id, session] of sessions) {
if (!isIdleExpired(session, now)) {
continue;
}
if (session.status === "running") {
session.child.kill("SIGTERM");
// Prevent killing the process repeatedly before it closes, and allow
// the "close" event to transition it to "exited" for the grace period.
session.lastActivityAt = new Date().toISOString();
} else {
sessions.delete(id);
}
}
};

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 57054845dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +47 to +49
if (session.status === "running") {
return now - lastActivity > IDLE_TIMEOUT_MS;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid killing quiet terminal jobs as idle

For running sessions, this treats the absence of input/output for 10 minutes as expiration, and purgeIdleSessions() then sends SIGTERM before any GET/list/create response. A user running a legitimate quiet command such as sleep 900, a long build step with no output, or a paused debugger will have the process killed and the session removed even though it is still active; terminal reads do not refresh lastActivityAt, so simply keeping the terminal open does not prevent this.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds lifecycle controls around server-side terminal sessions to reduce resource leaks, and updates the pnpm lockfile to reflect additional workspace package dependency graph changes.

Changes:

  • Add idle session expiry + purge logic and a hard cap on the number of concurrent terminal sessions.
  • Invoke idle-session purging on terminal session list/get/create operations.
  • Update pnpm-lock.yaml with new/updated workspace importer dependency resolutions (including new package entries and resolved versions).

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/lib/server/terminal-sessions.ts Introduces idle timeout purging and a max-session guard for in-memory terminal sessions.
pnpm-lock.yaml Updates pnpm lock state for workspace packages and their resolved dependency versions.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +156 to 160
export const listTerminalSessions = (): TerminalSessionSummary[] => {
purgeIdleSessions();
return [...sessions.values()]
.map(toSummary)
.sort((left, right) => right.startedAt.localeCompare(left.startedAt));
Comment on lines +173 to +177
purgeIdleSessions();
if (sessions.size >= MAX_SESSIONS) {
throw new Error(
`Terminal session limit reached (${MAX_SESSIONS}). Close an existing session before opening a new one.`
);
Comment on lines +156 to 167
export const listTerminalSessions = (): TerminalSessionSummary[] => {
purgeIdleSessions();
return [...sessions.values()]
.map(toSummary)
.sort((left, right) => right.startedAt.localeCompare(left.startedAt));
};

export const getTerminalSession = (id: string): TerminalSessionSnapshot | null => {
purgeIdleSessions();
const session = sessions.get(id);
return session ? toSnapshot(session) : null;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Idle timeout not guaranteed 🐞 Bug ☼ Reliability

Idle session eviction only happens when
listTerminalSessions/getTerminalSession/createTerminalSession are called, so abandoned sessions can
keep their shell processes alive indefinitely if no further API requests occur. This defeats the
intent of IDLE_TIMEOUT_MS and can leak OS processes and memory until the server receives another
terminal-related request.
Agent Prompt
### Issue description
`purgeIdleSessions()` is only invoked opportunistically (on list/get/create). If a client disconnects and stops calling these endpoints, idle sessions will never be purged and the underlying shell processes can continue running.

### Issue Context
The code defines `IDLE_TIMEOUT_MS`/`EXPIRED_OUTPUT_GRACE_MS`, but there is no background timer or per-session timer to enforce expiry.

### Fix Focus Areas
- src/lib/server/terminal-sessions.ts[34-179]

### Suggested fix
- Add a background `setInterval(() => purgeIdleSessions(), N)` (and `.unref()` if appropriate) so expiry is enforced even without inbound requests.
- Alternatively (often better), attach a per-session timer on creation that is reset whenever `lastActivityAt` changes; on timer fire, kill and cleanup that specific session.
- Add a small vitest to simulate time passing and verify sessions are purged without calling list/get/create.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +54 to +65
const purgeIdleSessions = (now: number = Date.now()): void => {
for (const [id, session] of sessions) {
if (!isIdleExpired(session, now)) {
continue;
}

if (session.status === "running") {
session.child.kill("SIGTERM");
}

sessions.delete(id);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Orphaned terminal processes 🐞 Bug ☼ Reliability

purgeIdleSessions() sends SIGTERM and immediately deletes the session from the map; if the child
ignores SIGTERM or takes time to exit, it can keep running with no remaining handle for
escalation/cleanup. This can leak long-lived processes and their event listeners/output buffers
beyond the session’s lifecycle.
Agent Prompt
### Issue description
Expired sessions are removed from `sessions` immediately after sending SIGTERM. If the process does not exit promptly, it becomes unmanaged and may continue consuming resources.

### Issue Context
`purgeIdleSessions()` and `closeTerminalSession()` both call `child.kill('SIGTERM')` and then delete the session right away.

### Fix Focus Areas
- src/lib/server/terminal-sessions.ts[54-66]
- src/lib/server/terminal-sessions.ts[264-276]

### Suggested fix
- When expiring/closing a running session:
  - Mark it as closing (optional), send SIGTERM.
  - Wait for the child's `close` event (or a short timeout).
  - If it does not close within the timeout, send SIGKILL.
  - Only then delete from the map.
- Ensure listeners are removed on cleanup to avoid retaining session objects longer than necessary.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@nehraa

nehraa commented Jun 11, 2026

Copy link
Copy Markdown
Owner Author

Closing as superseded by #41. The cleanup commit a896899 moved the target file from src/lib/server/terminal-sessions.ts to packages/codeflow-store/src/shared/terminal-sessions.ts, putting this PR in a CONFLICTING state.

PR #41 opens a fresh branch from origin/main and applies the same review fixes (Gemini CRITICAL on SIGTERM/grace period, Qodo #1 background scheduler, Qodo #2 SIGKILL escalation, Copilot on writeTerminalInput, plus the React 19 peerDeps fix from packages/codeflow-canvas/package.json) to the new file location. 11/11 new tests pass.

Two follow-up issues from the code-quality review are tracked separately (not blocking this PR):

  • shutdownAllTerminalSessions bypasses the SIGKILL escalation path
  • writeTerminalInput does a full purgeIdleSessions per write (O(N) per keystroke)

No action needed on this branch.

@nehraa nehraa closed this Jun 11, 2026
@nehraa
nehraa deleted the claude-lane/task_1780281408931_2vxzzbu7l-20260602125813 branch June 11, 2026 06:04
nehraa added a commit that referenced this pull request Jun 11, 2026
…SIGKILL escalation (#41)

* fix(codeflow-store): terminal-sessions purge, grace period, and SIGKILL escalation

Apply review feedback from PR #30:

  - Fix A (Gemini CRITICAL): purgeIdleSessions no longer drops a running
    idle session from the map. Instead, the session is SIGTERMed (with
    SIGKILL escalation) and its lastActivityAt is refreshed; the existing
    close handler transitions the entry to "exited", and a future purge
    cycle deletes it after EXPIRED_OUTPUT_GRACE_MS. This preserves the
    60 s grace period semantics for callers holding the snapshot.

  - Fix C (Qodo): SIGTERM is now paired with a 5 s SIGKILL escalation
    timer (killWithEscalation helper). The timer is unref'd so it does
    not keep the event loop alive. closeTerminalSession waits for the
    close event before deleting the map entry, so a SIGTERM in flight
    cannot leave a leaked session pointer.

  - Fix B (Qodo): a module-level setInterval (30 s) sweeps the session
    map, so idle sessions are reaped even when no inbound API call
    triggers purgeIdleSessions. The interval is unref'd and gated on a
    lazy "first call" guard so test re-imports cannot double-start it.

  - Fix D (Copilot): writeTerminalInput calls purgeIdleSessions at the
    top so a client cannot POST input to a long-idle session and revive
    it. Defense in depth alongside the background scheduler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(codeflow-store): cover terminal-sessions purge, grace, and SIGKILL

Add a vitest suite for the four fix areas from PR #30 review:

  - Fix A: purgeIdleSessions does not remove a running session from
    the map; its lastActivityAt is refreshed; the close handler
    transitions status to "exited" and a future purge cycle deletes
    the entry after EXPIRED_OUTPUT_GRACE_MS.

  - Fix B: a module-level scheduler is started by the first
    createTerminalSession call and not double-started on subsequent
    creates.

  - Fix C: closeTerminalSession sends SIGTERM and schedules a
    SIGKILL escalation timer; the map entry is removed only after
    the close event.

  - Fix D: writeTerminalInput calls purgeIdleSessions and rejects
    writes against a long-idle (now exited) session.

Tests use real child processes (the shell is the system of record)
and manipulate the system clock with vi.setSystemTime to drive
IDLE_TIMEOUT_MS and EXPIRED_OUTPUT_GRACE_MS cutoffs without waiting
real-world time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(codeflow-canvas): align React peerDeps with monorepo React 19 root

The monorepo root (packages/Codeflow_master) installs react and
react-dom at ^19.0.0, but the canvas package pinned ^18.0.0 in
both dependencies and peerDependencies. pnpm reported these as
unmet-peer conflicts on every install.

  - Move `react` and `react-dom` out of `dependencies` (where they
    shadowed the consumer's React) and into `devDependencies` (where
    the package's own build, type-check, and tests need them).
  - Widen `peerDependencies` to `^18 || ^19` so consumers on either
    React major can install.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: nehraa <nehraa@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
nehraa added a commit that referenced this pull request Jun 11, 2026
…SIGKILL escalation (#41)

* fix(codeflow-store): terminal-sessions purge, grace period, and SIGKILL escalation

Apply review feedback from PR #30:

  - Fix A (Gemini CRITICAL): purgeIdleSessions no longer drops a running
    idle session from the map. Instead, the session is SIGTERMed (with
    SIGKILL escalation) and its lastActivityAt is refreshed; the existing
    close handler transitions the entry to "exited", and a future purge
    cycle deletes it after EXPIRED_OUTPUT_GRACE_MS. This preserves the
    60 s grace period semantics for callers holding the snapshot.

  - Fix C (Qodo): SIGTERM is now paired with a 5 s SIGKILL escalation
    timer (killWithEscalation helper). The timer is unref'd so it does
    not keep the event loop alive. closeTerminalSession waits for the
    close event before deleting the map entry, so a SIGTERM in flight
    cannot leave a leaked session pointer.

  - Fix B (Qodo): a module-level setInterval (30 s) sweeps the session
    map, so idle sessions are reaped even when no inbound API call
    triggers purgeIdleSessions. The interval is unref'd and gated on a
    lazy "first call" guard so test re-imports cannot double-start it.

  - Fix D (Copilot): writeTerminalInput calls purgeIdleSessions at the
    top so a client cannot POST input to a long-idle session and revive
    it. Defense in depth alongside the background scheduler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(codeflow-store): cover terminal-sessions purge, grace, and SIGKILL

Add a vitest suite for the four fix areas from PR #30 review:

  - Fix A: purgeIdleSessions does not remove a running session from
    the map; its lastActivityAt is refreshed; the close handler
    transitions status to "exited" and a future purge cycle deletes
    the entry after EXPIRED_OUTPUT_GRACE_MS.

  - Fix B: a module-level scheduler is started by the first
    createTerminalSession call and not double-started on subsequent
    creates.

  - Fix C: closeTerminalSession sends SIGTERM and schedules a
    SIGKILL escalation timer; the map entry is removed only after
    the close event.

  - Fix D: writeTerminalInput calls purgeIdleSessions and rejects
    writes against a long-idle (now exited) session.

Tests use real child processes (the shell is the system of record)
and manipulate the system clock with vi.setSystemTime to drive
IDLE_TIMEOUT_MS and EXPIRED_OUTPUT_GRACE_MS cutoffs without waiting
real-world time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(codeflow-canvas): align React peerDeps with monorepo React 19 root

The monorepo root (packages/Codeflow_master) installs react and
react-dom at ^19.0.0, but the canvas package pinned ^18.0.0 in
both dependencies and peerDependencies. pnpm reported these as
unmet-peer conflicts on every install.

  - Move `react` and `react-dom` out of `dependencies` (where they
    shadowed the consumer's React) and into `devDependencies` (where
    the package's own build, type-check, and tests need them).
  - Widen `peerDependencies` to `^18 || ^19` so consumers on either
    React major can install.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: nehraa <nehraa@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants