# Task 3 Report: Failed tasks are scheduler-"completed"

## Status: COMPLETE

## What was done

**Bug:** `dispatcher.py` unconditionally called `result.completed[task_id] = task_result` and `scheduler.mark_completed(task_id)` for any dict-returning handler result, regardless of `task_result["success"]`. A `{"success": False, ...}` result (the normal task_handler failure return shape) would silently mark the node completed in the scheduler, causing `are_dependencies_satisfied` to return True for dependents — which then dispatched against a target branch missing the parent's work.

**Fix applied:** `claw_forge/orchestrator/dispatcher.py` lines 468-472 — replaced the unconditional completed path with a branch:

```python
if isinstance(task_result, dict) and task_result.get("success") is False:
    result.failed[task_id] = str(task_result.get("output", "task failed"))
    self._scheduler.mark_failed(task_id)
else:
    result.completed[task_id] = task_result or {}
    self._scheduler.mark_completed(task_id)
    if self._reviewer is not None:
        self._reviewer.notify_feature_completed()
```

The check uses `is False` (explicit identity) so `success=None`, `success=True`, or missing `success` key all fall through to the completed path — backward compatible with legacy/generic result dicts that don't include a `success` key.

The fatal_auth and deferred branches (lines 443-467) are preserved untouched and take precedence.

## Test file

`tests/orchestrator/test_dispatcher_failed_result.py` — 4 tests:

1. `test_dict_result_with_success_false_marks_failed_not_completed` — core bug regression test
2. `test_dict_result_with_success_true_still_completes` — positive case
3. `test_dict_result_with_no_success_key_still_completes` — legacy compat (no success key → completed)
4. `test_success_false_blocks_dependent_task` — end-to-end: failed parent blocks dependent dispatch

## Test summary

- New tests: 4 passed (2 failed in RED phase for correct reasons, all 4 green after fix)
- Full orchestrator suite + test_dispatcher.py: **116 passed, 0 failed**
- Lint: clean (`ruff check claw_forge/ tests/`)
- No pre-existing test was asserting the old buggy "failed dict lands in completed" behavior — no test updates required.

## Concerns

None. The fix is minimal, surgical, and backward compatible. The `is False` identity check ensures no behavior change for the majority of result shapes that don't carry `success=False`.
