Skip to content

Conversation

@code-yeongyu
Copy link
Owner

@code-yeongyu code-yeongyu commented Feb 1, 2026

Summary

  • Implement new unified Claude Tasks system with single multi-action tool replacing 4 separate tools
  • Add task-reminder hook for proactive task awareness during conversations
  • Remove legacy sisyphus-swarm and sisyphus-tasks features

Changes

New Features

  • Unified Task Tool (src/tools/task/): Single tool with 5 actions (create, list, get, update, delete) using action-based dispatch pattern
    • Auto-generates T-{uuid} task IDs
    • Records threadID for session tracking
    • Supports task dependencies with dependsOn field
    • Includes ready filter for tasks with all dependencies completed
  • Task Reminder Hook (src/hooks/task-reminder/): Reminds agents about pending tasks after tool executions
  • Claude Tasks Storage (src/features/claude-tasks/): Flat directory structure with JSON-based task files and atomic writes

Configuration

  • Add new_task_system_enabled top-level flag in config schema
  • Add disabled_tools setting for disabling specific tools
  • Add task-related tool permissions in config handler

Cleanup

  • Remove src/features/sisyphus-swarm/ (mailbox system)
  • Remove src/features/sisyphus-tasks/ (legacy task system)
  • Update tsconfig to exclude test files and scripts from build

Other

  • Add type guard in dynamic-truncator for non-string outputs
  • Add execute option in delegate-task types

Testing

bun run typecheck
bun test

All 768 lines of new tests for the unified Task tool covering:

  • All 5 actions (create, list, get, update, delete)
  • Edge cases: missing args, invalid IDs, status filters
  • Dependency filtering with ready flag
  • Concurrent access with locking

Related Issues


Summary by cubic

Unifies task management into a single “task” tool with create/list/get/update/delete actions and adds a reminder hook to nudge agents to track work. Removes the legacy Sisyphus Tasks/Swarm and introduces safer storage with atomic writes and file locking.

  • New Features

    • Single “task” tool with 5 actions, dependency support, and a ready filter.
    • Auto T-{uuid} IDs, threadID tracking, JSON storage, atomic writes, and file-based locks.
    • Task reminder hook triggers after 10 non-task tool calls.
    • Config updates: new_task_system_enabled flag, disabled_tools setting, and expanded agent permissions.
    • Stability: non-string output guard in truncator; delegate-task gains an optional execute field.
  • Migration

    • Enable with new_task_system_enabled to register the “task” tool.
    • Use tool name task with action to replace prior separate task tools.
    • Legacy sisyphus-swarm and sisyphus-tasks are removed; tasks stored under .sisyphus/tasks.

Written for commit 6acd5e8. Summary will update on new commits.

justsisyphus and others added 23 commits February 1, 2026 22:23
Remove mailbox types and swarm config schema. Update docs.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Remove old storage and types implementation, replaced by claude-tasks.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
- Task schema with Zod validation (pending, in_progress, completed, deleted)
- Storage utilities: getTaskDir, readJsonSafe, writeJsonAtomic, acquireLock
- Atomic writes with temp file + rename
- File-based locking with 30s stale threshold

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Add Zod schemas for task CRUD operations input validation.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Create new tasks with sequential ID generation and lock-based concurrency.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Retrieve task by ID with null-safe handling.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Update tasks with status transitions and owner claim validation.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
- TaskList for summary view of all tasks
- Export all claude-tasks tool factories from index

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Remind agents to use task tools after 10 turns without task operations.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
… hook

- Add disabled_tools config option to disable specific tools by name
- Register tasks-todowrite-disabler hook name in schema

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Grant task_* and teammate permissions to atlas, sisyphus, prometheus, and sisyphus-junior agents.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Add optional execute field with task_id and task_dir for task-based delegation.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Prevent crashes when output is not a string by adding typeof checks.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
- Export SisyphusConfig and SisyphusTasksConfig types
- Add task_tool to TARGET_TOOLS list

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
- Add new_task_system_enabled to OhMyOpenCodeConfigSchema
- Remove enabled from SisyphusTasksConfigSchema (keep storage_path, claude_code_compat)
- Update index.ts to gate on new_task_system_enabled
- Update plugin-config.ts default for config initialization
- Update test configs in task.test.ts and storage.test.ts

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <[email protected]>
- Add explicit ToolDefinition return type to createTask function
- Fix planDemoteConfig to use 'subagent' mode instead of 'all'
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

9 issues found across 32 files

Confidence score: 2/5

  • High-severity security risk in src/tools/task/task.ts: unsanitized id enables path traversal and deletion of arbitrary JSON files via handleDelete.
  • Concurrency handling is fragile: acquireLock results are ignored in src/tools/task/task.ts, and src/features/claude-tasks/storage.ts has non-atomic lock acquisition and unsafe release, risking data corruption.
  • Score is low because multiple high-severity issues (security and locking) are concrete and likely to impact users.
  • Pay close attention to src/tools/task/task.ts and src/features/claude-tasks/storage.ts - path traversal deletion and lock race/ownership handling.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/features/claude-tasks/storage.ts">

<violation number="1" location="src/features/claude-tasks/storage.ts:78">
P1: Race condition in lock acquisition. The pattern `if (existsSync) ... writeFileSync` is not atomic. Two processes can simultaneously check, find no lock, and both write, causing a race where both believe they acquired the lock.

Use `writeFileSync` with the `{ flag: 'wx' }` option to ensure atomic creation, and handle the `EEXIST` error to check for staleness.</violation>

<violation number="2" location="src/features/claude-tasks/storage.ts:105">
P2: Unsafe lock release mechanism. The `release` function unconditionally deletes the lock file. If a lock was stolen by another process (due to staleness), the original owner will delete the new owner's lock upon completion.

Store a unique ID in the lock file and verify it matches before deletion.</violation>
</file>

<file name="src/features/claude-tasks/types.test.ts">

<violation number="1" location="src/features/claude-tasks/types.test.ts:20">
P1: The schema tested here is inconsistent with the implementation in `src/tools/task/task.ts`. The test explicitly rejects "open" as a status and requires `subject`/`blockedBy`, whereas the tool uses `status: "open"` and `title`/`dependsOn`.

The tool implementation must be updated to use the unified schema defined in `src/features/claude-tasks/types.ts` to match the design spec and these tests.</violation>
</file>

<file name="src/hooks/task-reminder/index.ts">

<violation number="1" location="src/hooks/task-reminder/index.ts:5">
P2: The reminder message instructs the model to use `TaskCreate` and `TaskUpdate`, but the PR replaces separate tools with a single `task` tool using actions (`create`, `update`).

Using nonexistent tool names may cause hallucinations or tool use errors. Update the message to reference the `task` tool with specific actions.</violation>

<violation number="2" location="src/hooks/task-reminder/index.ts:20">
P2: Session counters are stored in a Map but never cleaned up, leading to a memory leak as sessions accumulate. 

Add a 'session.deleted' event handler to remove session IDs from `sessionCounters` when sessions end, following the pattern in `src/hooks/context-window-monitor.ts`.</violation>
</file>

<file name="src/tools/task/task.ts">

<violation number="1" location="src/tools/task/task.ts:89">
P1: Concurrency Issue: The result of `acquireLock` is ignored. If the lock cannot be acquired (e.g. another process is writing), this code proceeds anyway, leading to potential data corruption. Check `lock.acquired` and abort if false.</violation>

<violation number="2" location="src/tools/task/task.ts:180">
P0: Security Risk: The `id` parameter is not sanitized, allowing path traversal (e.g. `../package`). In `handleDelete`, this vulnerability permits the deletion of any JSON file on the system (like `package.json`). Validate that the ID contains only safe characters.</violation>
</file>

<file name="src/plugin-config.ts">

<violation number="1" location="src/plugin-config.ts:116">
P1: Configuration merge logic is flawed for this field. Because `new_task_system_enabled` has a default value in the schema, `loadConfigFromPath` returns a value (false) even when the field is missing in the project config. This causes `mergeConfigs` to overwrite any user-level setting (e.g., true) with the project-level default (false).

Recommended fix: Remove `.default(false)` from the schema (make it optional) and apply the default value after merging.</violation>
</file>

<file name="src/features/claude-tasks/AGENTS.md">

<violation number="1" location="src/features/claude-tasks/AGENTS.md:43">
P2: Function signature mismatch: `getTaskDir` takes only `config`, not `teamName`.

The implementation in `src/features/claude-tasks/storage.ts` is `getTaskDir(config)`. It does not accept a team name and does not append it to the path.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

): Promise<string> {
const validatedArgs = TaskGetInputSchema.parse(args)
const taskDir = getTaskDir(config)
const taskPath = join(taskDir, `${validatedArgs.id}.json`)
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 1, 2026

Choose a reason for hiding this comment

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

P0: Security Risk: The id parameter is not sanitized, allowing path traversal (e.g. ../package). In handleDelete, this vulnerability permits the deletion of any JSON file on the system (like package.json). Validate that the ID contains only safe characters.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tools/task/task.ts, line 180:

<comment>Security Risk: The `id` parameter is not sanitized, allowing path traversal (e.g. `../package`). In `handleDelete`, this vulnerability permits the deletion of any JSON file on the system (like `package.json`). Validate that the ID contains only safe characters.</comment>

<file context>
@@ -0,0 +1,253 @@
+): Promise<string> {
+  const validatedArgs = TaskGetInputSchema.parse(args)
+  const taskDir = getTaskDir(config)
+  const taskPath = join(taskDir, `${validatedArgs.id}.json`)
+
+  const task = readJsonSafe(taskPath, TaskObjectSchema)
</file context>
Fix with Cubic

const lockPath = join(dirPath, ".lock")
const now = Date.now()

if (existsSync(lockPath)) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 1, 2026

Choose a reason for hiding this comment

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

P1: Race condition in lock acquisition. The pattern if (existsSync) ... writeFileSync is not atomic. Two processes can simultaneously check, find no lock, and both write, causing a race where both believe they acquired the lock.

Use writeFileSync with the { flag: 'wx' } option to ensure atomic creation, and handle the EEXIST error to check for staleness.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/claude-tasks/storage.ts, line 78:

<comment>Race condition in lock acquisition. The pattern `if (existsSync) ... writeFileSync` is not atomic. Two processes can simultaneously check, find no lock, and both write, causing a race where both believe they acquired the lock.

Use `writeFileSync` with the `{ flag: 'wx' }` option to ensure atomic creation, and handle the `EEXIST` error to check for staleness.</comment>

<file context>
@@ -0,0 +1,112 @@
+  const lockPath = join(dirPath, ".lock")
+  const now = Date.now()
+
+  if (existsSync(lockPath)) {
+    try {
+      const lockContent = readFileSync(lockPath, "utf-8")
</file context>
Fix with Cubic


test("rejects invalid status values", () => {
//#given
const invalidStatuses = ["open", "closed", "archived", ""]
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 1, 2026

Choose a reason for hiding this comment

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

P1: The schema tested here is inconsistent with the implementation in src/tools/task/task.ts. The test explicitly rejects "open" as a status and requires subject/blockedBy, whereas the tool uses status: "open" and title/dependsOn.

The tool implementation must be updated to use the unified schema defined in src/features/claude-tasks/types.ts to match the design spec and these tests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/claude-tasks/types.test.ts, line 20:

<comment>The schema tested here is inconsistent with the implementation in `src/tools/task/task.ts`. The test explicitly rejects "open" as a status and requires `subject`/`blockedBy`, whereas the tool uses `status: "open"` and `title`/`dependsOn`.

The tool implementation must be updated to use the unified schema defined in `src/features/claude-tasks/types.ts` to match the design spec and these tests.</comment>

<file context>
@@ -0,0 +1,174 @@
+
+  test("rejects invalid status values", () => {
+    //#given
+    const invalidStatuses = ["open", "closed", "archived", ""]
+
+    //#when
</file context>
Fix with Cubic

): Promise<string> {
const validatedArgs = TaskCreateInputSchema.parse(args)
const taskDir = getTaskDir(config)
const lock = acquireLock(taskDir)
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 1, 2026

Choose a reason for hiding this comment

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

P1: Concurrency Issue: The result of acquireLock is ignored. If the lock cannot be acquired (e.g. another process is writing), this code proceeds anyway, leading to potential data corruption. Check lock.acquired and abort if false.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tools/task/task.ts, line 89:

<comment>Concurrency Issue: The result of `acquireLock` is ignored. If the lock cannot be acquired (e.g. another process is writing), this code proceeds anyway, leading to potential data corruption. Check `lock.acquired` and abort if false.</comment>

<file context>
@@ -0,0 +1,253 @@
+): Promise<string> {
+  const validatedArgs = TaskCreateInputSchema.parse(args)
+  const taskDir = getTaskDir(config)
+  const lock = acquireLock(taskDir)
+
+  try {
</file context>
Fix with Cubic

// Load user config first (base)
let config: OhMyOpenCodeConfig =
loadConfigFromPath(userConfigPath, ctx) ?? {};
loadConfigFromPath(userConfigPath, ctx) ?? { new_task_system_enabled: false };
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 1, 2026

Choose a reason for hiding this comment

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

P1: Configuration merge logic is flawed for this field. Because new_task_system_enabled has a default value in the schema, loadConfigFromPath returns a value (false) even when the field is missing in the project config. This causes mergeConfigs to overwrite any user-level setting (e.g., true) with the project-level default (false).

Recommended fix: Remove .default(false) from the schema (make it optional) and apply the default value after merging.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/plugin-config.ts, line 116:

<comment>Configuration merge logic is flawed for this field. Because `new_task_system_enabled` has a default value in the schema, `loadConfigFromPath` returns a value (false) even when the field is missing in the project config. This causes `mergeConfigs` to overwrite any user-level setting (e.g., true) with the project-level default (false).

Recommended fix: Remove `.default(false)` from the schema (make it optional) and apply the default value after merging.</comment>

<file context>
@@ -113,7 +113,7 @@ export function loadPluginConfig(
   // Load user config first (base)
   let config: OhMyOpenCodeConfig =
-    loadConfigFromPath(userConfigPath, ctx) ?? {};
+    loadConfigFromPath(userConfigPath, ctx) ?? { new_task_system_enabled: false };
 
   // Override with project config
</file context>
Fix with Cubic

release: () => {
try {
if (existsSync(lockPath)) {
unlinkSync(lockPath)
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 1, 2026

Choose a reason for hiding this comment

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

P2: Unsafe lock release mechanism. The release function unconditionally deletes the lock file. If a lock was stolen by another process (due to staleness), the original owner will delete the new owner's lock upon completion.

Store a unique ID in the lock file and verify it matches before deletion.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/claude-tasks/storage.ts, line 105:

<comment>Unsafe lock release mechanism. The `release` function unconditionally deletes the lock file. If a lock was stolen by another process (due to staleness), the original owner will delete the new owner's lock upon completion.

Store a unique ID in the lock file and verify it matches before deletion.</comment>

<file context>
@@ -0,0 +1,112 @@
+    release: () => {
+      try {
+        if (existsSync(lockPath)) {
+          unlinkSync(lockPath)
+        }
+      } catch {
</file context>
Fix with Cubic


const TASK_TOOLS = new Set(["task"])
const TURN_THRESHOLD = 10
const REMINDER_MESSAGE = `
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 1, 2026

Choose a reason for hiding this comment

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

P2: The reminder message instructs the model to use TaskCreate and TaskUpdate, but the PR replaces separate tools with a single task tool using actions (create, update).

Using nonexistent tool names may cause hallucinations or tool use errors. Update the message to reference the task tool with specific actions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/task-reminder/index.ts, line 5:

<comment>The reminder message instructs the model to use `TaskCreate` and `TaskUpdate`, but the PR replaces separate tools with a single `task` tool using actions (`create`, `update`).

Using nonexistent tool names may cause hallucinations or tool use errors. Update the message to reference the `task` tool with specific actions.</comment>

<file context>
@@ -0,0 +1,45 @@
+
+const TASK_TOOLS = new Set(["task"])
+const TURN_THRESHOLD = 10
+const REMINDER_MESSAGE = `
+
+The task tools haven't been used recently. If you're working on tasks that would benefit from tracking progress, consider using TaskCreate to add new tasks and TaskUpdate to update task status (set to in_progress when starting, completed when done).`
</file context>
Fix with Cubic

}

export function createTaskReminderHook(_ctx: PluginInput) {
const sessionCounters = new Map<string, number>()
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 1, 2026

Choose a reason for hiding this comment

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

P2: Session counters are stored in a Map but never cleaned up, leading to a memory leak as sessions accumulate.

Add a 'session.deleted' event handler to remove session IDs from sessionCounters when sessions end, following the pattern in src/hooks/context-window-monitor.ts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/task-reminder/index.ts, line 20:

<comment>Session counters are stored in a Map but never cleaned up, leading to a memory leak as sessions accumulate. 

Add a 'session.deleted' event handler to remove session IDs from `sessionCounters` when sessions end, following the pattern in `src/hooks/context-window-monitor.ts`.</comment>

<file context>
@@ -0,0 +1,45 @@
+}
+
+export function createTaskReminderHook(_ctx: PluginInput) {
+  const sessionCounters = new Map<string, number>()
+
+  const toolExecuteAfter = async (input: ToolExecuteInput, output: ToolExecuteOutput) => {
</file context>
Fix with Cubic


## STORAGE UTILITIES

### getTaskDir(teamName, config)
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 1, 2026

Choose a reason for hiding this comment

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

P2: Function signature mismatch: getTaskDir takes only config, not teamName.

The implementation in src/features/claude-tasks/storage.ts is getTaskDir(config). It does not accept a team name and does not append it to the path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/claude-tasks/AGENTS.md, line 43:

<comment>Function signature mismatch: `getTaskDir` takes only `config`, not `teamName`.

The implementation in `src/features/claude-tasks/storage.ts` is `getTaskDir(config)`. It does not accept a team name and does not append it to the path.</comment>

<file context>
@@ -0,0 +1,102 @@
+
+## STORAGE UTILITIES
+
+### getTaskDir(teamName, config)
+
+Returns: `.sisyphus/tasks/{teamName}` (or custom path from config)
</file context>
Fix with Cubic

@code-yeongyu code-yeongyu merged commit 8d29a1c into dev Feb 1, 2026
8 checks passed
@code-yeongyu code-yeongyu deleted the code-yeongyu/claude-task-swarm branch February 1, 2026 13:42
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