Skip to content

feat: add error feedback and unstick functionality for stuck tasks#1859

Closed
dertuerke wants to merge 8 commits into
AndyMik90:developfrom
dertuerke:feat/unstick-subtasks-error-feedback
Closed

feat: add error feedback and unstick functionality for stuck tasks#1859
dertuerke wants to merge 8 commits into
AndyMik90:developfrom
dertuerke:feat/unstick-subtasks-error-feedback

Conversation

@dertuerke

@dertuerke dertuerke commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Summary

When task recovery fails (e.g., stuck subtasks with wrong file paths), the UI now provides clear feedback and an option to unstick subtasks.

  • Error Feedback: Shows error toast with stuck subtask count when recovery fails
  • Unstick Button: Adds "Unstick Subtasks" button in TaskWarnings component with stuck reasons displayed
  • CLI Command: Adds --unstick flag for manual recovery from command line

Changes

Backend

  • cli/main.py: Add --unstick CLI flag to clear stuck subtasks
  • services/recovery.py: Add get_stuck_subtasks() method

Frontend Main Process

  • execution-handlers.ts: Add TASK_GET_STUCK_INFO and TASK_UNSTICK_SUBTASKS IPC handlers
  • ipc.ts: Add new IPC channel constants
  • task-api.ts: Expose getStuckInfo and unstickSubtasks preload APIs

Frontend Renderer

  • TaskWarnings.tsx: Show stuck subtasks with reasons and unstick button
  • TaskCard.tsx: Show error toast on recovery failure
  • task-store.ts: Add getStuckInfo and unstickSubtasks store actions

i18n

  • Add translation strings for error messages and actions in English and French

Test Plan

  1. Create a task with invalid file paths in implementation plan (e.g., non-existent files)
  2. Run task until subtasks get stuck
  3. Click Recover - should see error toast with stuck subtask count
  4. Open Task Detail modal - should see stuck subtasks listed with reasons
  5. Click "Unstick Subtasks" - should clear stuck state
  6. Click Recover again - should now succeed

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • CLI: new --unstick flag to detect, clear, and report stuck subtasks.
    • UI: view stuck subtasks with reasons, loading/toast feedback, and a manual "Unstick Subtasks" action.
    • Frontend↔backend: new APIs/channels to fetch stuck-subtask info and perform unstick operations.
  • Localization

    • Added English and French strings for stuck/subtask recovery flows, actions, errors, and messages.
  • Types & Mocks

    • New stuck-subtask data type and matching mock methods for testing.

github-actions Bot and others added 2 commits January 30, 2026 21:29
When task recovery fails (e.g., stuck subtasks with wrong file paths),
the UI now provides clear feedback and an option to unstick subtasks.

Changes:
- Add error toast with stuck subtask count when recovery fails
- Add "Unstick Subtasks" button in TaskWarnings component
- Display stuck subtask reasons (file validation failures, etc.)
- Add CLI --unstick command for manual recovery
- Add get_stuck_subtasks() method to RecoveryManager

Backend:
- apps/backend/cli/main.py: Add --unstick flag
- apps/backend/services/recovery.py: Add get_stuck_subtasks()

Frontend:
- Add IPC handlers TASK_GET_STUCK_INFO and TASK_UNSTICK_SUBTASKS
- Add preload APIs getStuckInfo and unstickSubtasks
- Update TaskWarnings to show stuck subtasks with unstick button
- Update TaskCard to show error toast on recovery failure

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

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @dertuerke, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the handling of 'stuck' tasks by providing users with better visibility into why tasks are stalled and offering direct mechanisms to resolve these issues. It integrates both frontend and backend changes to deliver a more robust recovery experience, ensuring tasks can resume smoothly even after encountering unexpected states.

Highlights

  • Enhanced Error Feedback: Implemented clear error toasts in the UI when task recovery fails, specifically indicating if stuck subtasks are blocking progress.
  • Unstick Functionality in UI: Added an 'Unstick Subtasks' button within the TaskWarnings component, allowing users to view reasons for stuck subtasks and manually clear them.
  • CLI Unstick Command: Introduced a new --unstick flag to the CLI, enabling manual clearing of stuck subtasks from the command line for easier recovery.
Changelog
  • apps/backend/cli/main.py
    • Added a new --unstick CLI argument to clear stuck subtasks.
    • Implemented logic to handle the --unstick command, utilizing new recovery service functions.
  • apps/backend/recovery.py
    • Exported the get_stuck_subtasks function to make it accessible.
  • apps/backend/services/recovery.py
    • Implemented the get_stuck_subtasks method within RecoveryManager to retrieve details of stuck subtasks.
    • Added a module-level wrapper function get_stuck_subtasks for convenience.
  • apps/frontend/src/main/ipc-handlers/task/execution-handlers.ts
    • Added IPC handlers for TASK_GET_STUCK_INFO to fetch stuck subtask details.
    • Implemented IPC handlers for TASK_UNSTICK_SUBTASKS to clear stuck subtasks and reset their status to pending.
  • apps/frontend/src/preload/api/task-api.ts
    • Exposed getStuckInfo and unstickSubtasks functions to the renderer process via the preload API.
  • apps/frontend/src/renderer/components/TaskCard.tsx
    • Updated the recoverStuckTask call to check for and display an error toast with the count of stuck subtasks if recovery fails.
  • apps/frontend/src/renderer/components/task-detail/TaskDetailModal.tsx
    • Passed projectId and specId as props to the TaskWarnings component.
  • apps/frontend/src/renderer/components/task-detail/TaskWarnings.tsx
    • Introduced state management for stuck subtasks, loading, and unsticking processes.
    • Implemented a useEffect hook to load stuck subtask information when a task is stuck.
    • Added a handleUnstick function to clear stuck subtasks via the new API.
    • Updated the UI to display a list of stuck subtasks with their reasons and an 'Unstick Subtasks' button.
    • Integrated internationalization (i18n) for all new warning and action texts.
  • apps/frontend/src/renderer/lib/mocks/task-mock.ts
    • Added mock implementations for getStuckInfo and unstickSubtasks for testing purposes.
  • apps/frontend/src/renderer/stores/task-store.ts
    • Added getStuckInfo and unstickSubtasks store actions to interact with the Electron API.
  • apps/frontend/src/shared/constants/ipc.ts
    • Defined new IPC channel constants: TASK_GET_STUCK_INFO and TASK_UNSTICK_SUBTASKS.
  • apps/frontend/src/shared/i18n/locales/en/tasks.json
    • Added new English translation strings for actions like 'Unstick Subtasks', 'Recovering...', 'Recover & Restart Task', and 'Resume Task'.
    • Included new labels for 'Stuck Subtasks' and 'Checking stuck subtasks...'.
    • Added error messages for 'Recovery failed', 'stuck subtasks blocking progress', and 'Failed to unstick subtasks'.
    • Introduced warning messages for 'Task Appears Stuck' and 'Task Incomplete' with detailed descriptions.
  • apps/frontend/src/shared/i18n/locales/fr/tasks.json
    • Added French translation strings corresponding to all new English texts for actions, labels, errors, and warnings.
  • apps/frontend/src/shared/types/ipc.ts
    • Extended the ElectronAPI interface to include getStuckInfo and unstickSubtasks methods with their respective return types.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai

coderabbitai Bot commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds stuck-subtask discovery and manual "unstick" flows across backend (service + CLI) and frontend (IPC, preload API, renderer UI, types, mocks, and i18n). CLI gains --unstick to list/clear stuck subtasks and the normal build flow is bypassed when used.

Changes

Cohort / File(s) Summary
Backend CLI & Recovery shim
apps/backend/cli/main.py, apps/backend/recovery.py
Adds --unstick CLI flag handling to list/clear stuck subtasks; re-exports get_stuck_subtasks from services.recovery via recovery shim.
Backend Recovery Service
apps/backend/services/recovery.py
Adds RecoveryManager.get_stuck_subtasks() and module-level get_stuck_subtasks(spec_dir, project_dir) wrapper to return stuck-subtask info.
Frontend Main (IPC) handlers
apps/frontend/src/main/ipc-handlers/task/execution-handlers.ts
New IPC handlers TASK_GET_STUCK_INFO and TASK_UNSTICK_SUBTASKS: read attempt_history.json to return stuck list and clear/reset stuck subtasks (also updates worktree copy when present).
Frontend Preload / API surface
apps/frontend/src/preload/api/task-api.ts
Exposes getStuckInfo(projectId, specId) and unstickSubtasks(projectId, specId) that call the new IPC channels.
Frontend Renderer components
apps/frontend/src/renderer/components/TaskCard.tsx, apps/frontend/src/renderer/components/task-detail/TaskWarnings.tsx, apps/frontend/src/renderer/components/task-detail/TaskDetailModal.tsx
TaskCard requests stuck info on recovery failure; TaskWarnings now accepts projectId/specId, fetches and displays stuck subtasks and provides an Unstick action with loading/toast flows; TaskDetailModal passes new props.
Frontend State, Types & IPC consts
apps/frontend/src/renderer/stores/task-store.ts, apps/frontend/src/shared/types/ipc.ts, apps/frontend/src/shared/types/task.ts, apps/frontend/src/shared/constants/ipc.ts
Adds getStuckInfo/unstickSubtasks helpers in task-store; adds StuckSubtaskInfo type and ElectronAPI typings; registers new IPC channel constants.
Frontend Mocks
apps/frontend/src/renderer/lib/mocks/task-mock.ts
Adds mock implementations for getStuckInfo and unstickSubtasks.
Internationalization
apps/frontend/src/shared/i18n/locales/en/tasks.json, apps/frontend/src/shared/i18n/locales/fr/tasks.json
Adds localization keys for unsticking, stuck/incomplete warnings, errors, action labels, and messages in English and French.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as Frontend UI
    participant Preload as Preload/API
    participant Main as Main (IPC)
    participant Recovery as Recovery Service
    participant FS as File System

    User->>UI: Open warnings / recovery fails
    UI->>Preload: getStuckInfo(projectId, specId)
    Preload->>Main: TASK_GET_STUCK_INFO (projectId, specId)
    Main->>Recovery: get_stuck_subtasks(spec_dir, project_dir)
    Recovery->>FS: read attempt_history.json
    FS-->>Recovery: attempt history (stuck_subtasks)
    Recovery-->>Main: { stuckSubtasks }
    Main-->>Preload: { stuckSubtasks }
    Preload-->>UI: { stuckSubtasks }
    UI->>User: Render stuck list

    User->>UI: Click "Unstick"
    UI->>Preload: unstickSubtasks(projectId, specId)
    Preload->>Main: TASK_UNSTICK_SUBTASKS (projectId, specId)
    Main->>Recovery: clear_stuck_subtasks(spec_dir, project_dir)
    Recovery->>FS: write attempt_history.json (clear stuck), update subtask statuses
    FS-->>Recovery: persisted
    Recovery-->>Main: { cleared: N }
    Main-->>Preload: { cleared: N }
    Preload-->>UI: { cleared: N }
    UI->>User: Show toast and refresh UI
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Suggested Labels

feature, area/fullstack, size/M, 🔄 Checking

Suggested Reviewers

  • AndyMik90
  • AlexMadera
  • MikeeBuilds

Poem

🐇 I found a tangle deep in queue,
Tiny subtasks stuck, asking "who?"
I nudged a flag and rang the bell,
IPC hummed — the block fell well,
Now plans hop on, all fresh and new.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main objective of this PR: adding both error feedback and unstick functionality for stuck tasks, which are the two primary features across backend, IPC, and frontend components.
Docstring Coverage ✅ Passed Docstring coverage is 92.86% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@sentry

sentry Bot commented Feb 17, 2026

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces a helpful feature for handling stuck tasks by providing error feedback in the UI and a mechanism to "unstick" them. The changes span both the backend and frontend, adding a new CLI flag, IPC handlers, and UI components.

My review focuses on two main points:

  1. A suggestion to improve code clarity in the new CLI command handling in apps/backend/cli/main.py.
  2. A more significant concern about logic duplication in apps/frontend/src/main/ipc-handlers/task/execution-handlers.ts, where logic for handling stuck tasks is reimplemented in TypeScript instead of leveraging the Python backend. Centralizing this logic would improve maintainability.

Overall, the feature is a valuable addition for robustness. The frontend changes, in particular, are well-implemented to provide better user feedback.

Comment on lines +1228 to +1319
ipcMain.handle(
IPC_CHANNELS.TASK_GET_STUCK_INFO,
async (_, projectId: string, specId: string): Promise<IPCResult<{ stuckSubtasks: Array<{ subtask_id: string; reason: string; escalated_at: string; attempt_count: number }> }>> => {
try {
const project = projectStore.getProject(projectId);
if (!project) {
return { success: false, error: 'Project not found' };
}

const specsBaseDir = getSpecsDir(project.autoBuildPath);
const specDir = path.join(project.path, specsBaseDir, specId);
const attemptHistoryPath = path.join(specDir, 'memory', 'attempt_history.json');

if (!existsSync(attemptHistoryPath)) {
return { success: true, data: { stuckSubtasks: [] } };
}

const historyContent = safeReadFileSync(attemptHistoryPath);
if (!historyContent) {
return { success: true, data: { stuckSubtasks: [] } };
}

const history = JSON.parse(historyContent);
return { success: true, data: { stuckSubtasks: history.stuck_subtasks || [] } };
} catch (error) {
console.error('Failed to get stuck info:', error);
return { success: false, error: error instanceof Error ? error.message : 'Failed to get stuck info' };
}
}
);

/**
* Clear stuck subtasks for a spec
*/
ipcMain.handle(
IPC_CHANNELS.TASK_UNSTICK_SUBTASKS,
async (_, projectId: string, specId: string): Promise<IPCResult<{ cleared: number }>> => {
try {
const project = projectStore.getProject(projectId);
if (!project) {
return { success: false, error: 'Project not found' };
}

const specsBaseDir = getSpecsDir(project.autoBuildPath);
const specDir = path.join(project.path, specsBaseDir, specId);
const attemptHistoryPath = path.join(specDir, 'memory', 'attempt_history.json');

if (!existsSync(attemptHistoryPath)) {
return { success: true, data: { cleared: 0 } };
}

const historyContent = safeReadFileSync(attemptHistoryPath);
if (!historyContent) {
return { success: true, data: { cleared: 0 } };
}

const history = JSON.parse(historyContent);
const count = (history.stuck_subtasks || []).length;

// Clear stuck subtasks list
history.stuck_subtasks = [];

// Reset any subtasks marked as 'stuck' to 'pending'
if (history.subtasks) {
for (const subtaskId in history.subtasks) {
if (history.subtasks[subtaskId].status === 'stuck') {
history.subtasks[subtaskId].status = 'pending';
}
}
}

// Save updated history
writeFileAtomicSync(attemptHistoryPath, JSON.stringify(history, null, 2));

// Also update worktree copy if it exists
const worktreePath = findTaskWorktree(project.path, specId);
if (worktreePath) {
const worktreeSpecDir = path.join(worktreePath, specsBaseDir, specId);
const worktreeHistoryPath = path.join(worktreeSpecDir, 'memory', 'attempt_history.json');
if (existsSync(worktreeHistoryPath)) {
writeFileAtomicSync(worktreeHistoryPath, JSON.stringify(history, null, 2));
}
}

console.log(`[Unstick] Cleared ${count} stuck subtasks for ${specId}`);
return { success: true, data: { cleared: count } };
} catch (error) {
console.error('Failed to unstick subtasks:', error);
return { success: false, error: error instanceof Error ? error.message : 'Failed to unstick subtasks' };
}
}
);

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.

high

These new IPC handlers (TASK_GET_STUCK_INFO and TASK_UNSTICK_SUBTASKS) duplicate logic that exists or should exist in the Python backend. For example, they directly read and manipulate attempt_history.json, which is also handled by services/recovery.py.

Duplicating this logic across the TypeScript main process and the Python backend can lead to inconsistencies and maintenance issues. For instance, the logic in TASK_UNSTICK_SUBTASKS to reset subtask statuses from 'stuck' to 'pending' is more complete than the current Python clear_stuck_subtasks function, which only clears the stuck_subtasks array.

To improve maintainability and ensure a single source of truth, this logic should be centralized in the Python backend. The services/recovery.py module should be updated to fully handle unsticking tasks (including resetting statuses), and these IPC handlers should then execute the backend logic, for example, by shelling out to a CLI command. The --unstick flag has already been added, and a similar one could be added for getting stuck info.

Comment thread apps/backend/cli/main.py
Comment on lines +475 to +487
if args.unstick:
from services.recovery import get_stuck_subtasks as get_stuck
from services.recovery import clear_stuck_subtasks as clear_stuck

stuck = get_stuck(spec_dir, project_dir)
if stuck:
clear_stuck(spec_dir, project_dir)
print(f"Cleared {len(stuck)} stuck subtasks for {args.spec}")
for s in stuck:
print(f" - {s.get('subtask_id', 'unknown')}: {s.get('reason', 'no reason')[:80]}")
else:
print(f"No stuck subtasks found for {args.spec}")
return

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.

medium

The aliases get_stuck and clear_stuck can be confusing, especially since clear_stuck is used as a function name but it's an alias for clear_stuck_subtasks. For better readability and maintainability, it's recommended to use the full function names directly by changing the import statement and the subsequent calls.

Suggested change
if args.unstick:
from services.recovery import get_stuck_subtasks as get_stuck
from services.recovery import clear_stuck_subtasks as clear_stuck
stuck = get_stuck(spec_dir, project_dir)
if stuck:
clear_stuck(spec_dir, project_dir)
print(f"Cleared {len(stuck)} stuck subtasks for {args.spec}")
for s in stuck:
print(f" - {s.get('subtask_id', 'unknown')}: {s.get('reason', 'no reason')[:80]}")
else:
print(f"No stuck subtasks found for {args.spec}")
return
if args.unstick:
from services.recovery import get_stuck_subtasks, clear_stuck_subtasks
stuck = get_stuck_subtasks(spec_dir, project_dir)
if stuck:
clear_stuck_subtasks(spec_dir, project_dir)
print(f"Cleared {len(stuck)} stuck subtasks for {args.spec}")
for s in stuck:
print(f" - {s.get('subtask_id', 'unknown')}: {s.get('reason', 'no reason')[:80]}")
else:
print(f"No stuck subtasks found for {args.spec}")
return

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/backend/cli/main.py`:
- Around line 476-477: The two separate imports from the same module should be
combined into a single import to satisfy the linter: replace the two lines
importing get_stuck_subtasks and clear_stuck_subtasks from services.recovery
with one statement that imports both symbols (get_stuck_subtasks,
clear_stuck_subtasks) in a single from ... import ... line; update any local
aliasing if needed (e.g., if you previously aliased one as get_stuck or
clear_stuck, preserve those aliases in the combined import).

In `@apps/backend/services/recovery.py`:
- Around line 580-588: The file defines duplicate methods named
get_stuck_subtasks on class RecoveryManager; remove the earlier/first definition
so only the later implementation (with the more detailed docstring and the call
to self._load_attempt_history) remains. Locate the first get_stuck_subtasks
definition in class RecoveryManager and delete that entire method block,
ensuring there is only one get_stuck_subtasks method (the version returning
history.get("stuck_subtasks", [])) to resolve the Ruff F811 duplicate-definition
error.

In `@apps/frontend/src/main/ipc-handlers/task/execution-handlers.ts`:
- Around line 1228-1257: Remove the redundant existsSync check and rely on
safeReadFileSync's ENOENT handling: in the IPC handler registered under
IPC_CHANNELS.TASK_GET_STUCK_INFO, delete the existsSync(attemptHistoryPath)
branch and simply call safeReadFileSync(attemptHistoryPath), return an empty
stuckSubtasks array when safeReadFileSync returns null/empty, then parse JSON
when non-null; apply the same simplification to the TASK_UNSTICK_SUBTASKS
handler (remove existsSync usage and handle null from safeReadFileSync before
parsing) so there is no TOCTOU gap and behavior remains the same.
- Around line 1284-1297: The loop that iterates subtasks using "for (const
subtaskId in history.subtasks)" can pick up prototype-inherited keys; change it
to iterate only own keys (e.g., Object.keys(history.subtasks).forEach(...) or
use Object.hasOwn(history.subtasks, subtaskId) inside the loop) and preserve the
existing behavior of checking status === 'stuck' and setting it to
'pending'—apply this change where history is parsed and the subtasks loop is
implemented to defensively guard against inherited properties.

In `@apps/frontend/src/renderer/components/task-detail/TaskWarnings.tsx`:
- Around line 43-56: The effect may apply stale getStuckInfo results when
projectId/specId change quickly; fix useEffect by introducing a local
canceled/ignore flag (e.g., let cancelled = false) inside the useEffect, pass
projectId/specId to getStuckInfo as before, and before calling
setStuckSubtasks(...) or setIsLoadingStuck(false) ensure !cancelled; in the
cleanup return () => { cancelled = true } so resolved promises from prior calls
don't overwrite newer state (also guard the else branch behavior accordingly).
- Around line 5-6: Replace the deep relative imports in TaskWarnings.tsx with
the project's tsconfig path aliases: change imports of getStuckInfo and
unstickSubtasks from '../../stores/task-store' to '@/stores/task-store' (maps to
src/renderer/stores) and change useToast from '../../hooks/use-toast' to
'@hooks/use-toast' (maps to src/renderer/shared/hooks); update the import
statements where getStuckInfo, unstickSubtasks, and useToast are referenced so
module resolution uses the configured aliases.

In `@apps/frontend/src/renderer/stores/task-store.ts`:
- Around line 988-999: The function unstickSubtasks currently returns the raw
IPCResult from window.electronAPI.unstickSubtasks, which nests the payload under
result.data so callers never see cleared; update unstickSubtasks to inspect the
returned result, extract cleared from result.data (safely handling when data is
undefined), and return a flattened object like { success: result.success,
cleared: result.data?.cleared, error: result.error } while preserving the
existing try/catch error handling; reference the unstickSubtasks function and
the call to window.electronAPI.unstickSubtasks when making the change (see
getStuckInfo for the correct extraction pattern).

In `@apps/frontend/src/shared/types/ipc.ts`:
- Around line 208-209: The duplicated inline stuck-subtask shape should be
extracted to a single shared interface (e.g., export interface StuckSubtaskInfo
{ subtask_id: string; reason: string; escalated_at: string; attempt_count:
number }) placed in the shared types module (suggested:
apps/frontend/src/shared/types/task.ts), then replace the inline type
occurrences in IPC signatures getStuckInfo and unstickSubtasks (in ipc.ts) and
the corresponding types/usages in task-api.ts, task-store.ts, and
TaskWarnings.tsx to reference StuckSubtaskInfo (and update any IPCResult
generics to use Array<StuckSubtaskInfo>) so all four locations share the same
definition.

Comment thread apps/backend/cli/main.py Outdated
Comment thread apps/backend/services/recovery.py
Comment thread apps/frontend/src/main/ipc-handlers/task/execution-handlers.ts
Comment thread apps/frontend/src/main/ipc-handlers/task/execution-handlers.ts
Comment thread apps/frontend/src/renderer/components/task-detail/TaskWarnings.tsx Outdated
Comment thread apps/frontend/src/renderer/components/task-detail/TaskWarnings.tsx
Comment thread apps/frontend/src/renderer/stores/task-store.ts
Comment thread apps/frontend/src/shared/types/ipc.ts Outdated
Comment on lines +208 to +209
getStuckInfo: (projectId: string, specId: string) => Promise<IPCResult<{ stuckSubtasks: Array<{ subtask_id: string; reason: string; escalated_at: string; attempt_count: number }> }>>;
unstickSubtasks: (projectId: string, specId: string) => Promise<IPCResult<{ cleared: number }>>;

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.

🛠️ Refactor suggestion | 🟠 Major

Extract the duplicated stuck-subtask shape into a shared type.

The inline type { subtask_id: string; reason: string; escalated_at: string; attempt_count: number } is repeated verbatim in at least four places: ipc.ts, task-api.ts, task-store.ts, and TaskWarnings.tsx. A single shared interface would reduce duplication and make future changes (e.g., adding fields) safer.

♻️ Suggested approach

Define a shared type in apps/frontend/src/shared/types/task.ts (or wherever TaskRecoveryResult lives):

export interface StuckSubtaskInfo {
  subtask_id: string;
  reason: string;
  escalated_at: string;
  attempt_count: number;
}

Then reference it in all four locations:

- getStuckInfo: (projectId: string, specId: string) => Promise<IPCResult<{ stuckSubtasks: Array<{ subtask_id: string; reason: string; escalated_at: string; attempt_count: number }> }>>;
+ getStuckInfo: (projectId: string, specId: string) => Promise<IPCResult<{ stuckSubtasks: StuckSubtaskInfo[] }>>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend/src/shared/types/ipc.ts` around lines 208 - 209, The duplicated
inline stuck-subtask shape should be extracted to a single shared interface
(e.g., export interface StuckSubtaskInfo { subtask_id: string; reason: string;
escalated_at: string; attempt_count: number }) placed in the shared types module
(suggested: apps/frontend/src/shared/types/task.ts), then replace the inline
type occurrences in IPC signatures getStuckInfo and unstickSubtasks (in ipc.ts)
and the corresponding types/usages in task-api.ts, task-store.ts, and
TaskWarnings.tsx to reference StuckSubtaskInfo (and update any IPCResult
generics to use Array<StuckSubtaskInfo>) so all four locations share the same
definition.

- Fix Ruff I001: combine duplicate imports in cli/main.py
- Fix Ruff F811: remove duplicate get_stuck_subtasks method in recovery.py
- Fix bug where unstickSubtasks toast never fired (return raw IPCResult without flattening)
- Add race condition fix in TaskWarnings useEffect with ignore flag
- Remove redundant existsSync checks (TOCTOU fix)
- Use Object.keys() instead of for...in for prototype safety
- Use path aliases instead of deep relative imports
- Extract StuckSubtaskInfo into shared type

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/backend/cli/main.py`:
- Around line 474-486: The long f-strings in the --unstick block (when
args.unstick) are tripping Ruff; modify the print statements around
get_stuck_subtasks and clear_stuck_subtasks to break long f-strings into shorter
expressions or concatenated parts (e.g., assign the message to a local variable
like msg = f"...{args.spec}" or use two prints), and truncate the reason using
slicing or textwrap.shorten into a separate variable before printing; update the
calls that print each stuck subtask (the f"{s.get('subtask_id', 'unknown')}:
{s.get('reason', 'no reason')[:80]}") to build the subtask_id and reason into
small variables and then print them to satisfy Ruff line-length/formatting
checks.

In `@apps/frontend/src/main/ipc-handlers/task/execution-handlers.ts`:
- Around line 1233-1251: Replace hardcoded English error strings in this IPC
handler with i18n keys and optional params: instead of returning { success:
false, error: 'Project not found' } return something like { success: false,
errorKey: 'error.projectNotFound' }, and in the catch block return { success:
false, errorKey: 'error.failedToGetStuckInfo', errorParams: { message: error
instanceof Error ? error.message : undefined } }; update any other similar
returns in this function (references: project, specsBaseDir, specDir,
attemptHistoryPath, history) and ensure corresponding keys are added to both
en/*.json and fr/*.json; keep translation responsibility in the renderer (use
react-i18next to translate errorKey + errorParams when showing toasts).

In `@apps/frontend/src/renderer/components/task-detail/TaskWarnings.tsx`:
- Around line 96-99: The JSX currently hardcodes the truncation suffix "..." for
stuck.reason in TaskWarnings.tsx; replace the hardcoded string by using the
react-i18next translator (e.g., call t('taskWarnings.truncationSuffix') or
similar) where the suffix is appended, and add the corresponding key/value to
both the en and fr JSON resource files (e.g., "taskWarnings": {
"truncationSuffix": "..." } and the French equivalent); alternatively, remove
the manual suffix and rely on CSS truncation (keep text in <span
className="truncate">) if preferred, but ensure all user-facing text uses
react-i18next and the new key is added to both en and fr locales.

In `@apps/frontend/src/renderer/stores/task-store.ts`:
- Line 3: Replace the relative import of shared types with the frontend path
alias: change the import that currently pulls "Task, TaskStatus, SubtaskStatus,
ImplementationPlan, Subtask, TaskMetadata, ExecutionProgress, ExecutionPhase,
ReviewReason, TaskDraft, ImageAttachment, TaskOrderState, StuckSubtaskInfo" from
'../../shared/types' to use the '@shared' alias (e.g., '@shared/types') so the
module resolution follows the project's tsconfig alias conventions; update the
import statement in task-store.ts where those symbols are referenced.

Comment on lines +1233 to +1251
if (!project) {
return { success: false, error: 'Project not found' };
}

const specsBaseDir = getSpecsDir(project.autoBuildPath);
const specDir = path.join(project.path, specsBaseDir, specId);
const attemptHistoryPath = path.join(specDir, 'memory', 'attempt_history.json');

const historyContent = safeReadFileSync(attemptHistoryPath);
if (!historyContent) {
return { success: true, data: { stuckSubtasks: [] } };
}

const history = JSON.parse(historyContent);
return { success: true, data: { stuckSubtasks: history.stuck_subtasks || [] } };
} catch (error) {
console.error('Failed to get stuck info:', error);
return { success: false, error: error instanceof Error ? error.message : 'Failed to get stuck info' };
}

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.

⚠️ Potential issue | 🟠 Major

Return i18n keys (not hardcoded strings) in IPC error payloads.
These error messages are surfaced to the renderer (e.g., toast descriptions), so they should be localized. Prefer returning an error key/params and translating in the UI layer rather than emitting raw English strings here.

As per coding guidelines: **/*.{tsx,ts}: All frontend user-facing text MUST use react-i18next translation keys. Never hardcode strings in JSX/TSX. Add keys to both en/*.json and fr/*.json.

Also applies to: 1263-1309

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend/src/main/ipc-handlers/task/execution-handlers.ts` around lines
1233 - 1251, Replace hardcoded English error strings in this IPC handler with
i18n keys and optional params: instead of returning { success: false, error:
'Project not found' } return something like { success: false, errorKey:
'error.projectNotFound' }, and in the catch block return { success: false,
errorKey: 'error.failedToGetStuckInfo', errorParams: { message: error instanceof
Error ? error.message : undefined } }; update any other similar returns in this
function (references: project, specsBaseDir, specDir, attemptHistoryPath,
history) and ensure corresponding keys are added to both en/*.json and
fr/*.json; keep translation responsibility in the renderer (use react-i18next to
translate errorKey + errorParams when showing toasts).

Comment on lines +96 to +99
<span className="font-mono">{stuck.subtask_id}</span>:{' '}
<span className="truncate" title={stuck.reason}>
{stuck.reason.length > 80 ? `${stuck.reason.slice(0, 80)}...` : stuck.reason}
</span>

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.

⚠️ Potential issue | 🟡 Minor

Localize the truncation suffix instead of hardcoding "...".
Use an i18n key for the truncated form (or rely on CSS truncation) and add the key to both en and fr.

🌍 Possible i18n-safe approach
-                          {stuck.reason.length > 80 ? `${stuck.reason.slice(0, 80)}...` : stuck.reason}
+                          {stuck.reason.length > 80
+                            ? t('labels.truncatedReason', { reason: stuck.reason.slice(0, 80) })
+                            : stuck.reason}

As per coding guidelines: **/*.{tsx,ts}: All frontend user-facing text MUST use react-i18next translation keys. Never hardcode strings in JSX/TSX. Add keys to both en/*.json and fr/*.json.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend/src/renderer/components/task-detail/TaskWarnings.tsx` around
lines 96 - 99, The JSX currently hardcodes the truncation suffix "..." for
stuck.reason in TaskWarnings.tsx; replace the hardcoded string by using the
react-i18next translator (e.g., call t('taskWarnings.truncationSuffix') or
similar) where the suffix is appended, and add the corresponding key/value to
both the en and fr JSON resource files (e.g., "taskWarnings": {
"truncationSuffix": "..." } and the French equivalent); alternatively, remove
the manual suffix and rely on CSS truncation (keep text in <span
className="truncate">) if preferred, but ensure all user-facing text uses
react-i18next and the new key is added to both en and fr locales.

import { create } from 'zustand';
import { arrayMove } from '@dnd-kit/sortable';
import type { Task, TaskStatus, SubtaskStatus, ImplementationPlan, Subtask, TaskMetadata, ExecutionProgress, ExecutionPhase, ReviewReason, TaskDraft, ImageAttachment, TaskOrderState } from '../../shared/types';
import type { Task, TaskStatus, SubtaskStatus, ImplementationPlan, Subtask, TaskMetadata, ExecutionProgress, ExecutionPhase, ReviewReason, TaskDraft, ImageAttachment, TaskOrderState, StuckSubtaskInfo } from '../../shared/types';

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.

⚠️ Potential issue | 🟡 Minor

Use the @shared path alias for shared types.
This import should follow the frontend alias conventions.

♻️ Suggested change
-import type { Task, TaskStatus, SubtaskStatus, ImplementationPlan, Subtask, TaskMetadata, ExecutionProgress, ExecutionPhase, ReviewReason, TaskDraft, ImageAttachment, TaskOrderState, StuckSubtaskInfo } from '../../shared/types';
+import type { Task, TaskStatus, SubtaskStatus, ImplementationPlan, Subtask, TaskMetadata, ExecutionProgress, ExecutionPhase, ReviewReason, TaskDraft, ImageAttachment, TaskOrderState, StuckSubtaskInfo } from '@shared/types';

As per coding guidelines: apps/frontend/**/*.{ts,tsx}: Use path aliases defined in tsconfig.json: @/*src/renderer/*, @shared/*src/shared/*, @preload/*src/preload/*, @features/*src/renderer/features/*, @components/*src/renderer/shared/components/*, @hooks/*src/renderer/shared/hooks/*, @lib/*src/renderer/shared/lib/*.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { Task, TaskStatus, SubtaskStatus, ImplementationPlan, Subtask, TaskMetadata, ExecutionProgress, ExecutionPhase, ReviewReason, TaskDraft, ImageAttachment, TaskOrderState, StuckSubtaskInfo } from '../../shared/types';
import type { Task, TaskStatus, SubtaskStatus, ImplementationPlan, Subtask, TaskMetadata, ExecutionProgress, ExecutionPhase, ReviewReason, TaskDraft, ImageAttachment, TaskOrderState, StuckSubtaskInfo } from '@shared/types';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend/src/renderer/stores/task-store.ts` at line 3, Replace the
relative import of shared types with the frontend path alias: change the import
that currently pulls "Task, TaskStatus, SubtaskStatus, ImplementationPlan,
Subtask, TaskMetadata, ExecutionProgress, ExecutionPhase, ReviewReason,
TaskDraft, ImageAttachment, TaskOrderState, StuckSubtaskInfo" from
'../../shared/types' to use the '@shared' alias (e.g., '@shared/types') so the
module resolution follows the project's tsconfig alias conventions; update the
import statement in task-store.ts where those symbols are referenced.

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/backend/cli/main.py`:
- Around line 474-488: The --unstick flag is currently handled outside the
mutually-exclusive build_group so args.unstick can be ignored when combined with
other actions; add the --unstick option to the same argparse mutually-exclusive
group (build_group) where --merge/--review/--discard etc. are defined and remove
the standalone handling block that checks args.unstick in main.py, ensuring
unstick is rejected when combined with other action flags and handled uniformly
with the other build_group actions.

Ensures --unstick is rejected when combined with other action flags
like --merge, --review, --discard, or --create-pr.

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/backend/cli/main.py`:
- Around line 472-486: Wrap the call to clear_stuck_subtasks in a targeted
try/except inside main() around the existing --unstick handling: call
get_stuck_subtasks first as is, and when stuck is truthy, call
clear_stuck_subtasks within a try block and on exception print a clear CLI
message like "Failed to clear stuck subtasks: <error>" (including the exception
text) and return a non-zero exit or propagate appropriately; keep the existing
success prints when no exception occurs. Ensure you reference
clear_stuck_subtasks and get_stuck_subtasks and do not change the overall
control flow for the --unstick branch.

Wraps the clear_stuck_subtasks call in try/except and exits with
non-zero status on failure, providing clear error message to user.

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/backend/cli/main.py`:
- Around line 472-490: The current unstick flow calls get_stuck_subtasks outside
the try/except, so exceptions from get_stuck_subtasks bubble up; move the call
to get_stuck_subtasks(spec_dir, project_dir) inside the same try block that
calls clear_stuck_subtasks, and catch exceptions there (same except Exception as
e) to print a contextual "Failed to clear stuck subtasks: {e}" message and
sys.exit(1); keep the existing logic that prints "No stuck subtasks found for
{args.spec}" when get_stuck_subtasks returns empty.

Comment thread apps/backend/cli/main.py
Comment on lines +472 to +490
# Handle --unstick command
if args.unstick:
from services.recovery import clear_stuck_subtasks, get_stuck_subtasks

stuck = get_stuck_subtasks(spec_dir, project_dir)
if stuck:
try:
clear_stuck_subtasks(spec_dir, project_dir)
print(f"Cleared {len(stuck)} stuck subtasks for {args.spec}")
for s in stuck:
print(
f" - {s.get('subtask_id', 'unknown')}: {s.get('reason', 'no reason')[:80]}"
)
except Exception as e:
print(f"Failed to clear stuck subtasks: {e}")
sys.exit(1)
else:
print(f"No stuck subtasks found for {args.spec}")
return

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.

⚠️ Potential issue | 🟡 Minor

Wrap get_stuck_subtasks in the same error-handling block.

If get_stuck_subtasks on line 476 raises (e.g., corrupted/missing history file), the exception bubbles to the outer main() handler and the user sees a generic "Unexpected error: ..." instead of a contextual CLI message. Consider moving it inside the try block for a consistent experience.

Proposed fix
     # Handle --unstick command
     if args.unstick:
         from services.recovery import clear_stuck_subtasks, get_stuck_subtasks
 
-        stuck = get_stuck_subtasks(spec_dir, project_dir)
-        if stuck:
-            try:
+        try:
+            stuck = get_stuck_subtasks(spec_dir, project_dir)
+            if stuck:
                 clear_stuck_subtasks(spec_dir, project_dir)
                 print(f"Cleared {len(stuck)} stuck subtasks for {args.spec}")
                 for s in stuck:
                     print(
                         f"  - {s.get('subtask_id', 'unknown')}: {s.get('reason', 'no reason')[:80]}"
                     )
-            except Exception as e:
-                print(f"Failed to clear stuck subtasks: {e}")
-                sys.exit(1)
-        else:
-            print(f"No stuck subtasks found for {args.spec}")
+            else:
+                print(f"No stuck subtasks found for {args.spec}")
+        except Exception as e:
+            print(f"Failed to clear stuck subtasks: {e}")
+            sys.exit(1)
         return
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Handle --unstick command
if args.unstick:
from services.recovery import clear_stuck_subtasks, get_stuck_subtasks
stuck = get_stuck_subtasks(spec_dir, project_dir)
if stuck:
try:
clear_stuck_subtasks(spec_dir, project_dir)
print(f"Cleared {len(stuck)} stuck subtasks for {args.spec}")
for s in stuck:
print(
f" - {s.get('subtask_id', 'unknown')}: {s.get('reason', 'no reason')[:80]}"
)
except Exception as e:
print(f"Failed to clear stuck subtasks: {e}")
sys.exit(1)
else:
print(f"No stuck subtasks found for {args.spec}")
return
# Handle --unstick command
if args.unstick:
from services.recovery import clear_stuck_subtasks, get_stuck_subtasks
try:
stuck = get_stuck_subtasks(spec_dir, project_dir)
if stuck:
clear_stuck_subtasks(spec_dir, project_dir)
print(f"Cleared {len(stuck)} stuck subtasks for {args.spec}")
for s in stuck:
print(
f" - {s.get('subtask_id', 'unknown')}: {s.get('reason', 'no reason')[:80]}"
)
else:
print(f"No stuck subtasks found for {args.spec}")
except Exception as e:
print(f"Failed to clear stuck subtasks: {e}")
sys.exit(1)
return
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/cli/main.py` around lines 472 - 490, The current unstick flow
calls get_stuck_subtasks outside the try/except, so exceptions from
get_stuck_subtasks bubble up; move the call to get_stuck_subtasks(spec_dir,
project_dir) inside the same try block that calls clear_stuck_subtasks, and
catch exceptions there (same except Exception as e) to print a contextual
"Failed to clear stuck subtasks: {e}" message and sys.exit(1); keep the existing
logic that prints "No stuck subtasks found for {args.spec}" when
get_stuck_subtasks returns empty.

Both get_stuck_subtasks and clear_stuck_subtasks are now protected
by the same exception handler.

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

CLAassistant commented Feb 18, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ dertuerke
❌ github-actions[bot]
You have signed the CLA already but the status is still pending? Let us recheck it.

@dertuerke

Copy link
Copy Markdown
Contributor Author

@AndyMik90 i have sign the CLA but they say no?

@AndyMik90

Copy link
Copy Markdown
Owner

👋 Thanks so much for your interest in contributing to Aperant!

Pull requests are currently paused. We're rebuilding the entire app from the ground up for Aperant 3.0 (including new cloud features). Most of that work is happening in a separate development repo right now and will be merged here once it's ready — so although this repo looks quiet, the project is very much alive. 🚀

Because the current codebase is being fully replaced, we're closing existing PRs so contributors aren't left waiting on changes that unfortunately can't be merged. This is not a reflection on your work, and we're sorry for any wasted effort. 🙏

We'd genuinely love your help shaping 3.0 instead:

Contributions will reopen once the 3.0 foundation lands. Thank you for understanding!

@AndyMik90 AndyMik90 closed this Jun 14, 2026
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.

3 participants