BOHICA-LABS / BOHICA-LABS/vsdd-factory

bug(factory-obs): dispatcher races with /factory-health worktree setup by continuously recreating .factory/logs/

Open
#206 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
2
Forks
1
Avg merge
6h 43m
Merged PRs (30d)
29

Description

## Summary

The factory-obs dispatcher, once registered against a project, persistently recreates `.factory/logs/` and writes `dispatcher-internal-*.jsonl` even when `.factory/` does not exist or has just been moved aside. This races with `/vsdd-factory:factory-health`'s worktree setup steps and causes downstream failures (see related issue on nested-worktree mount).

## Repro

1. `factory-obs register` against a fresh project. `.factory/logs/` is created with dispatcher-internal logs.
2. `mv .factory /tmp/factory-bak` — `.factory/` no longer exists.
3. Within seconds, `.factory/logs/dispatcher-internal-.jsonl` reappears with fresh content.
4. Repeat the move — same result.

Observed during `/factory-health` repair: across three move-aside operations, the dispatcher recreated `.factory/logs/` three times, each with overlapping but non-identical content.

## Why this is a problem

- **Data overlap.** Each recreation produces a new `dispatcher-internal-.jsonl`. If the operator moves `.factory/` aside, then the dispatcher writes a new file with the same name into the freshly-recreated `.factory/logs/`. Restoring the original loses the new writes; restoring the new loses the old writes.
- **Worktree-mount race.** `git worktree add .factory ...` requires `.factory/` to be empty/absent. The dispatcher recreating `.factory/` mid-setup causes the worktree to mount at the wrong path (see related issue).
- **Surprise mutation.** The operator cannot reason about `.factory/` state because it changes underneath them.

## Proposed fix

### Option A: write dispatcher-internal logs OUTSIDE the project (recommended)

`dispatcher-internal-*.jsonl` is **not** application/event data — it's the dispatcher's own diagnostic log. It does not belong inside the user's repo. Move these to:

- `~/.factory-obs/dispatchers//` or
- `~/Library/Logs/factory-obs//` (macOS) / `~/.local/state/factory-obs//` (Linux)

Only application event JSONL (the `events-*.jsonl` that the OTel collector tails) should live in `.factory/logs/`.

### Option B: dispatcher only writes when .factory/ is a confirmed worktree

The dispatcher should verify `.factory/` is a git worktree on `factory-artifacts` before opening any log file. If not, it should write to a fallback path outside the repo and emit a warning.

```python
def resolve_logs_dir(project_root):
factory = project_root / ".factory"
try:
toplevel = subprocess.check_output(
["git", "-C", str(factory), "rev-parse", "--show-toplevel"]
).decode().strip()
if Path(toplevel) == factory:
return factory / "logs"
except subprocess.CalledProcessError:
pass
# Fallback: write outside repo
return Path.home() / ".factory-obs" / "dispatchers" / hash(project_root) / "logs"
```

### Option C: dispatcher pauses on a sentinel

Factory-health writes `.factory-health-in-progress` at the project root; the dispatcher checks for this before each write and skips if present. Factory-health removes the sentinel when done.

## Acceptance criteria

- [ ] Moving `.factory/` aside does not cause the dispatcher to recreate it.
- [ ] `dispatcher-internal-*.jsonl` is not written inside the user's repo (or, if it is, only when `.factory/` is a confirmed worktree).
- [ ] `/factory-health` can complete without racing the dispatcher.

## Found during

`/factory-health` on `switchboard-blue` (2026-06-23, `vsdd-factory@1.0.0-rc.21`) immediately after `/onboard-observability`. The dispatcher recreated `.factory/logs/` three times across the move-aside / worktree-add sequence, producing overlapping log files and ultimately causing the worktree to mount at `.factory/.factory/`.

## Related

- #130 (closed) — recursive `.factory/.factory/logs/` shadow when dispatcher cwd resolves into `.factory/`.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.