Skip to content

fix(watch-tasks): honor SUTANDO_WORKSPACE for default TASKS_DIR - #736

Merged
sonichi merged 2 commits into
mainfrom
worktree-fix-watcher-workspace
May 16, 2026
Merged

sonichi merged 2 commits into
mainfrom
worktree-fix-watcher-workspace

Conversation

@qingyun-wu

Copy link
Copy Markdown
Collaborator

Summary

Aligns src/watch-tasks-stream.sh with the rest of the bridges (discord, telegram, dm-result), which all honor \$SUTANDO_WORKSPACE per PRs #708/#720/#722/#723. Without this, when Sutando.app sets SUTANDO_WORKSPACE (which it always does), the bridges write tasks to the workspace but the watcher polls the repo — owner DMs land but the agent never sees them.

Diagnosed live

2026-05-15 — three Discord DMs were stranded in `$SUTANDO_WORKSPACE/tasks/` for ~17 min before the manual workaround (restart the Monitor with an explicit positional arg). Detailed in build_log.

Resolution priority

  1. explicit positional arg → back-compat for callers that pass an explicit dir
  2. `$SUTANDO_WORKSPACE/tasks` → matches the bridges
  3. repo-relative fallback → matches old default for non-workspace setups

Test plan

  • `bash src/watch-tasks-stream.sh` (no env, no arg) → watches repo's `tasks/` (unchanged behavior).
  • `SUTANDO_WORKSPACE=/tmp/wsp bash src/watch-tasks-stream.sh` → watches `/tmp/wsp/tasks` (new behavior).
  • `bash src/watch-tasks-stream.sh /custom/dir` → watches `/custom/dir` (explicit-arg back-compat).

🤖 Generated with Claude Code

qingyun-wu and others added 2 commits May 15, 2026 20:20
The bridges (discord-bridge.py, telegram-bridge.py, dm-result.py) all
honor $SUTANDO_WORKSPACE for their tasks-dir resolution — see
PRs #708/#720/#722/#723. But watch-tasks-stream.sh was still defaulting
to the repo-relative $(dirname $0)/../tasks, so when Sutando.app sets
SUTANDO_WORKSPACE (which it always does), the bridges write tasks to
the workspace but the watcher polls the repo. Owner DMs land but the
agent never sees them.

Diagnosed live 2026-05-15: three Discord DMs were stranded in
$SUTANDO_WORKSPACE/tasks/ for ~17 min before the workaround (restart
the Monitor with an explicit positional arg).

Resolution priority is now:
  1. explicit positional arg (back-compat)
  2. $SUTANDO_WORKSPACE/tasks (matches bridges)
  3. repo-relative fallback (matches old default for non-workspace setups)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

LGTM. Clean three-way priority (explicit arg → $SUTANDO_WORKSPACE/tasks → repo-relative fallback). Aligns with the bridge convention from #708/#720/#722/#723. Back-compat preserved for both edges (explicit positional + no-env-no-arg). Bash logic correct: ${1:-} and ${SUTANDO_WORKSPACE:-} both safe under set -u. Diagnosis details (3 DMs / 17min) clearly identify the failure mode. Safe to merge once CI clears.

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

LGTM. Matches the #708/#720/#722/#723 SUTANDO_WORKSPACE convention — last bridge that was inconsistent. Self-merge whenever.

@sonichi
sonichi merged commit 429704c into main May 16, 2026
2 checks passed
@sonichi
sonichi deleted the worktree-fix-watcher-workspace branch May 16, 2026 06:41
qingyun-wu added a commit that referenced this pull request May 17, 2026
…readers (#793)

* feat(health-check): honor SUTANDO_WORKSPACE for tasks/ + state/ paths

Three call-sites in src/health-check.py hardcoded REPO_DIR / "tasks" or
REPO_DIR / "state". When the owner pointed $SUTANDO_WORKSPACE at a non-
repo path (PR #762), the watcher started reading from
$SUTANDO_WORKSPACE/tasks/ while health-check kept writing alerts to
<repo>/tasks/ — three task-health alerts on 2026-05-16 landed in the
wrong dir before being archived manually. Same drift class as
src/watch-tasks-stream.sh pre-#736 and skills/self-diagnose pre-#769.

Switches:
- check_task_queue (line 624)
- emit_task_for_failures defaults (state + tasks)
- notify_for_failures default state file

Source-code paths (src/, skills/, logs/, .env, build_log.md) keep using
REPO_DIR. Tests updated to mutate WORKSPACE_DIR instead of REPO_DIR.

Stacks on #762's feat/workspace-default-app-support-subdir.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(phone-conversation): accept TWILIO_WEBHOOK_URL as alternative to NGROK_AUTHTOKEN

Startup gate at conversation-server.ts:154 hard-required NGROK_AUTHTOKEN
even when TWILIO_WEBHOOK_URL was set (e.g., a Cloudflare/Tailscale tunnel).
The runtime tunnel-selection at line ~1654 already handled
TWILIO_WEBHOOK_URL — only the startup gate was wrong, causing the server
to exit immediately and return 502 at the tunnel for every inbound call.

Result on env-pinned hosts: phone was silently broken from 2026-05-13
through 2026-05-17 (Twilio inbound → tunnel → :3100 dead).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sync-memory): derive MEMORY_DIR from SCRIPT_PARENT, not SUTANDO_WORKSPACE

scripts/sync-memory.sh:83 computed MEMORY_DIR by sed-mapping REPO_DIR
(which is SUTANDO_WORKSPACE or $HOME/Desktop/sutando) to a Claude
project key. Claude actually keys its project memory dir on the
launch CWD (where the user typed `claude` from), NOT on SUTANDO_WORKSPACE.
On any host where the two differ — i.e., any host that follows PR #762's
"workspace separate from repo" convention — the sed-mapped key resolves
to a path that doesn't exist, then the fallback `find … | head -1` picks
whichever sibling memory dir comes first alphabetically.

Result on this MacBookPro host: sync-memory.sh has been syncing the
WRONG dir since at least 2026-04-11 (date on the oldest memory file
that never propagated). Every "Sync complete: N files" output reported
success while silently skipping real memory writes. Discovered
2026-05-17 13:46 PT; manual `rsync && git push` repaired the gap by
hand (commit e58204e in qingyun-wu/dotsutando).

Fix: derive MEMORY_DIR from SCRIPT_PARENT (the script's own parent.parent
→ the repo root → the Claude-launch CWD on any sane install). NOTES_DIR
is unchanged: it correctly follows SUTANDO_WORKSPACE because notes ARE
workspace state, not source-tree state. The two-variable distinction
matches the REPO_DIR vs WORKSPACE_DIR pattern PR #775 just landed for
agent-api.py + github-webhook.py + task-bridge.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(util-paths): honor SUTANDO_WORKSPACE in personal_path + shared_personal_path

Both helpers defaulted the `workspace` arg to REPO_DIR (the source tree).
On hosts that pin SUTANDO_WORKSPACE to a path separate from the repo
(the design PR #762 encourages), every caller that didn't explicitly
pass a workspace argument got the source-tree path back instead of the
workspace path — so notes/build_log/pending-questions/contextual-chips/
core-status resolution silently used the wrong directory.

Concrete impact today: agent writes to `<repo>/notes/` instead of
`<workspace>/notes/`, divergence between where bridges read and where
agents write, the same workspace-vs-repo conflation bug class PRs #762,
#775, and the upcoming health-check/sync-memory fixes are addressing.

Fix: new `_workspace_root()` helper honors SUTANDO_WORKSPACE first, falls
back to REPO_DIR for back-compat. Default arg in both functions switches
from REPO_DIR to `_workspace_root()`. Explicit caller overrides (passing
`workspace=...`) keep working unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(dashboard): honor SUTANDO_WORKSPACE for build_log + personal_path callers

dashboard.py read build_log + pending-questions from REPO_DIR (the
source tree) instead of WORKSPACE_DIR. On hosts that pin
SUTANDO_WORKSPACE to a separate path — i.e., the design PR #762
recommends — the dashboard showed a stale-or-empty build_log relative
to where the proactive loop actually wrote.

Fix:
1. Add WORKSPACE_DIR = resolve_workspace() alongside the preserved
   REPO_DIR (two-variable split per PR #775).
2. Switch the two build_log path constructions from REPO_DIR to
   WORKSPACE_DIR.
3. Drop the explicit `REPO_DIR` argument from personal_path /
   shared_personal_path calls. The util_paths.py fix in the prior
   commit makes the default honor SUTANDO_WORKSPACE — so callers that
   pass no workspace argument now get the workspace path automatically,
   matching the contract.

Preserved REPO_DIR usage:
- exec'ing src/health-check.py (source path)
- `cwd=REPO_DIR` for `git log` calls (git tree)
- quota-state.json fallback chain (out of scope; existing fallback works)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(workspace-contract): codify REPO_DIR vs WORKSPACE_DIR split

Formalizes the contract that PRs #762, #775, and this PR's individual
fixes have been converging on. Audience: OSS contributors deciding
where to put new files, plus future-us debugging the next iteration of
this bug class.

Covers:
- The two-variable split (source tree vs runtime state) + when each
  applies.
- Python / TypeScript / Shell resolution patterns.
- Four common mistakes (the bug class this contract prevents), each
  cross-referenced to a fix in this PR.
- A 5-step decision tree for new code ("walk top-to-bottom, stop at
  the first match").
- Cross-references to PR #762 and PR #775 so the lineage of the fix
  is discoverable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs+fix: tighten the rule — REPO_DIR is source-tree-only

Owner-clarified 2026-05-17: REPO_DIR shall not be used for any
user/runtime path; only source-code, exec'd scripts, and git-tree
metadata get to use REPO_DIR. Earlier drafts of this PR framed the
two-variable split as co-equal "use whichever is semantically correct";
the stronger framing is "default to WORKSPACE_DIR; REPO_DIR is the
unusual case that needs justification."

Three changes apply that stronger rule:

1. docs/workspace-contract.md — rewrite the lead to state the rule
   directly. Add a "code review test" section: for every REPO_DIR /
   "..." in a PR diff, ask "is this source code, an exec'd script, or
   a git cwd?" — if no, it's the bug pattern.

2. src/util_paths.py _workspace_root() — fallback was REPO_DIR for
   "back-compat"; that was a workaround that perpetuated the bug
   class. Now delegates to workspace_default.resolve_workspace()
   which handles SUTANDO_WORKSPACE + the canonical default
   (~/.sutando/workspace/) + the legacy migration in one call. The
   inline fallback for environments where workspace_default isn't
   importable uses the canonical default, never REPO_DIR.

3. src/dashboard.py get_quota_status() — quota-state.json IS runtime
   state, so the primary read path is now WORKSPACE_DIR. The legacy
   skill-dir path is preserved as a fallback only for the existing
   writer (skills/quota-tracker/) until that's migrated separately
   (out of scope for this PR).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(health-check): check new memory-sync path (~/.sutando/memory-sync/)

PR #764 renamed the memory-sync clone dir from ~/.sutando-memory-sync/
to ~/.sutando/memory-sync/, but src/health-check.py:118 still pointed
at the legacy path. Result: any host that's completed the auto-migration
sees a persistent "memory-sync: repo configured but never synced" warn
even though sync is healthy and running on cron — health-check just
can't see it.

Fix: check the new canonical path first; fall back to the legacy path
for installs mid-migration; same warn message but updated to reference
the script's actual location (scripts/sync-memory.sh, not the legacy
~/.sutando-memory-sync/scripts/ path).

Belongs in this PR because it's the same workspace-path-drift bug
class — code wasn't updated after a path canonicalization. Surfaced
2026-05-17 22:36 via a health-check task on the running node.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…chi#736)

The bridges (discord-bridge.py, telegram-bridge.py, dm-result.py) all
honor $SUTANDO_WORKSPACE for their tasks-dir resolution — see
PRs sonichi#708/sonichi#720/sonichi#722/sonichi#723. But watch-tasks-stream.sh was still defaulting
to the repo-relative $(dirname $0)/../tasks, so when Sutando.app sets
SUTANDO_WORKSPACE (which it always does), the bridges write tasks to
the workspace but the watcher polls the repo. Owner DMs land but the
agent never sees them.

Diagnosed live 2026-05-15: three Discord DMs were stranded in
$SUTANDO_WORKSPACE/tasks/ for ~17 min before the workaround (restart
the Monitor with an explicit positional arg).

Resolution priority is now:
  1. explicit positional arg (back-compat)
  2. $SUTANDO_WORKSPACE/tasks (matches bridges)
  3. repo-relative fallback (matches old default for non-workspace setups)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…readers (sonichi#793)

* feat(health-check): honor SUTANDO_WORKSPACE for tasks/ + state/ paths

Three call-sites in src/health-check.py hardcoded REPO_DIR / "tasks" or
REPO_DIR / "state". When the owner pointed $SUTANDO_WORKSPACE at a non-
repo path (PR sonichi#762), the watcher started reading from
$SUTANDO_WORKSPACE/tasks/ while health-check kept writing alerts to
<repo>/tasks/ — three task-health alerts on 2026-05-16 landed in the
wrong dir before being archived manually. Same drift class as
src/watch-tasks-stream.sh pre-sonichi#736 and skills/self-diagnose pre-sonichi#769.

Switches:
- check_task_queue (line 624)
- emit_task_for_failures defaults (state + tasks)
- notify_for_failures default state file

Source-code paths (src/, skills/, logs/, .env, build_log.md) keep using
REPO_DIR. Tests updated to mutate WORKSPACE_DIR instead of REPO_DIR.

Stacks on sonichi#762's feat/workspace-default-app-support-subdir.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(phone-conversation): accept TWILIO_WEBHOOK_URL as alternative to NGROK_AUTHTOKEN

Startup gate at conversation-server.ts:154 hard-required NGROK_AUTHTOKEN
even when TWILIO_WEBHOOK_URL was set (e.g., a Cloudflare/Tailscale tunnel).
The runtime tunnel-selection at line ~1654 already handled
TWILIO_WEBHOOK_URL — only the startup gate was wrong, causing the server
to exit immediately and return 502 at the tunnel for every inbound call.

Result on env-pinned hosts: phone was silently broken from 2026-05-13
through 2026-05-17 (Twilio inbound → tunnel → :3100 dead).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sync-memory): derive MEMORY_DIR from SCRIPT_PARENT, not SUTANDO_WORKSPACE

scripts/sync-memory.sh:83 computed MEMORY_DIR by sed-mapping REPO_DIR
(which is SUTANDO_WORKSPACE or $HOME/Desktop/sutando) to a Claude
project key. Claude actually keys its project memory dir on the
launch CWD (where the user typed `claude` from), NOT on SUTANDO_WORKSPACE.
On any host where the two differ — i.e., any host that follows PR sonichi#762's
"workspace separate from repo" convention — the sed-mapped key resolves
to a path that doesn't exist, then the fallback `find … | head -1` picks
whichever sibling memory dir comes first alphabetically.

Result on this MacBookPro host: sync-memory.sh has been syncing the
WRONG dir since at least 2026-04-11 (date on the oldest memory file
that never propagated). Every "Sync complete: N files" output reported
success while silently skipping real memory writes. Discovered
2026-05-17 13:46 PT; manual `rsync && git push` repaired the gap by
hand (commit e58204e in qingyun-wu/dotsutando).

Fix: derive MEMORY_DIR from SCRIPT_PARENT (the script's own parent.parent
→ the repo root → the Claude-launch CWD on any sane install). NOTES_DIR
is unchanged: it correctly follows SUTANDO_WORKSPACE because notes ARE
workspace state, not source-tree state. The two-variable distinction
matches the REPO_DIR vs WORKSPACE_DIR pattern PR sonichi#775 just landed for
agent-api.py + github-webhook.py + task-bridge.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(util-paths): honor SUTANDO_WORKSPACE in personal_path + shared_personal_path

Both helpers defaulted the `workspace` arg to REPO_DIR (the source tree).
On hosts that pin SUTANDO_WORKSPACE to a path separate from the repo
(the design PR sonichi#762 encourages), every caller that didn't explicitly
pass a workspace argument got the source-tree path back instead of the
workspace path — so notes/build_log/pending-questions/contextual-chips/
core-status resolution silently used the wrong directory.

Concrete impact today: agent writes to `<repo>/notes/` instead of
`<workspace>/notes/`, divergence between where bridges read and where
agents write, the same workspace-vs-repo conflation bug class PRs sonichi#762,
sonichi#775, and the upcoming health-check/sync-memory fixes are addressing.

Fix: new `_workspace_root()` helper honors SUTANDO_WORKSPACE first, falls
back to REPO_DIR for back-compat. Default arg in both functions switches
from REPO_DIR to `_workspace_root()`. Explicit caller overrides (passing
`workspace=...`) keep working unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(dashboard): honor SUTANDO_WORKSPACE for build_log + personal_path callers

dashboard.py read build_log + pending-questions from REPO_DIR (the
source tree) instead of WORKSPACE_DIR. On hosts that pin
SUTANDO_WORKSPACE to a separate path — i.e., the design PR sonichi#762
recommends — the dashboard showed a stale-or-empty build_log relative
to where the proactive loop actually wrote.

Fix:
1. Add WORKSPACE_DIR = resolve_workspace() alongside the preserved
   REPO_DIR (two-variable split per PR sonichi#775).
2. Switch the two build_log path constructions from REPO_DIR to
   WORKSPACE_DIR.
3. Drop the explicit `REPO_DIR` argument from personal_path /
   shared_personal_path calls. The util_paths.py fix in the prior
   commit makes the default honor SUTANDO_WORKSPACE — so callers that
   pass no workspace argument now get the workspace path automatically,
   matching the contract.

Preserved REPO_DIR usage:
- exec'ing src/health-check.py (source path)
- `cwd=REPO_DIR` for `git log` calls (git tree)
- quota-state.json fallback chain (out of scope; existing fallback works)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(workspace-contract): codify REPO_DIR vs WORKSPACE_DIR split

Formalizes the contract that PRs sonichi#762, sonichi#775, and this PR's individual
fixes have been converging on. Audience: OSS contributors deciding
where to put new files, plus future-us debugging the next iteration of
this bug class.

Covers:
- The two-variable split (source tree vs runtime state) + when each
  applies.
- Python / TypeScript / Shell resolution patterns.
- Four common mistakes (the bug class this contract prevents), each
  cross-referenced to a fix in this PR.
- A 5-step decision tree for new code ("walk top-to-bottom, stop at
  the first match").
- Cross-references to PR sonichi#762 and PR sonichi#775 so the lineage of the fix
  is discoverable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs+fix: tighten the rule — REPO_DIR is source-tree-only

Owner-clarified 2026-05-17: REPO_DIR shall not be used for any
user/runtime path; only source-code, exec'd scripts, and git-tree
metadata get to use REPO_DIR. Earlier drafts of this PR framed the
two-variable split as co-equal "use whichever is semantically correct";
the stronger framing is "default to WORKSPACE_DIR; REPO_DIR is the
unusual case that needs justification."

Three changes apply that stronger rule:

1. docs/workspace-contract.md — rewrite the lead to state the rule
   directly. Add a "code review test" section: for every REPO_DIR /
   "..." in a PR diff, ask "is this source code, an exec'd script, or
   a git cwd?" — if no, it's the bug pattern.

2. src/util_paths.py _workspace_root() — fallback was REPO_DIR for
   "back-compat"; that was a workaround that perpetuated the bug
   class. Now delegates to workspace_default.resolve_workspace()
   which handles SUTANDO_WORKSPACE + the canonical default
   (~/.sutando/workspace/) + the legacy migration in one call. The
   inline fallback for environments where workspace_default isn't
   importable uses the canonical default, never REPO_DIR.

3. src/dashboard.py get_quota_status() — quota-state.json IS runtime
   state, so the primary read path is now WORKSPACE_DIR. The legacy
   skill-dir path is preserved as a fallback only for the existing
   writer (skills/quota-tracker/) until that's migrated separately
   (out of scope for this PR).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(health-check): check new memory-sync path (~/.sutando/memory-sync/)

PR sonichi#764 renamed the memory-sync clone dir from ~/.sutando-memory-sync/
to ~/.sutando/memory-sync/, but src/health-check.py:118 still pointed
at the legacy path. Result: any host that's completed the auto-migration
sees a persistent "memory-sync: repo configured but never synced" warn
even though sync is healthy and running on cron — health-check just
can't see it.

Fix: check the new canonical path first; fall back to the legacy path
for installs mid-migration; same warn message but updated to reference
the script's actual location (scripts/sync-memory.sh, not the legacy
~/.sutando-memory-sync/scripts/ path).

Belongs in this PR because it's the same workspace-path-drift bug
class — code wasn't updated after a path canonicalization. Surfaced
2026-05-17 22:36 via a health-check task on the running node.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <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