Skip to content

fix(watch-tasks-stream): default fallback to ~/.sutando/workspace/tasks (matches bridge contract) - #779

Merged
sonichi merged 1 commit into
mainfrom
fix/watch-tasks-stream-default-to-canonical-workspace
May 17, 2026
Merged

sonichi merged 1 commit into
mainfrom
fix/watch-tasks-stream-default-to-canonical-workspace

Conversation

@sonichi

@sonichi sonichi commented May 17, 2026 •

Copy link
Copy Markdown
Owner

Summary

When $SUTANDO_WORKSPACE is unset AND no positional arg is given, watch-tasks-stream.sh was falling back to <repo>/tasks/ while every bridge (discord-bridge.py, telegram-bridge.py, dm-result.py) falls back to ~/.sutando/workspace/tasks/ via workspace_default.resolve_workspace(). Bridges and watcher diverge, owner DMs land but never reach the agent.

Switch the fallback to match the bridge contract.

Why now

  • 2026-05-15: ~3 dropped DMs over 17 min — workaround was a manual restart with the env exported.
  • 2026-05-16 (today): ~45 min silent gap on Chi's MacBook. The Monitor was started without SUTANDO_WORKSPACE in its env, watcher polled the legacy <repo>/tasks/, bridge wrote to ~/.sutando/workspace/tasks/. Chi: "this will be an issue for other sutandos too." Confirmed — same bug surfaces on any fresh install whose watcher launcher doesn't pin the env.

Related: #774 (docs warning for existing-repo installs to pin the env). This is the codepath fix that complements that doc change — fresh installs and any caller that forgot to export now still land on the bridge-canonical default.

Diff

-  TASKS_DIR="$(dirname "$0")/../tasks"
+  TASKS_DIR="$HOME/.sutando/workspace/tasks"

Plus comment update explaining the divergence and the two incidents.

Post-merge restart caveat

Per MacBook bot's review observation: any host that pulls this commit but doesn't restart its Monitor / watch-tasks-stream.sh process is briefly divergent in the opposite direction — new code on disk (env-unset → ~/.sutando/workspace/tasks/) but the still-running watcher loaded from the old code (env-unset → <repo>/tasks/). Smaller window than the bug being fixed, but same failure mode.

Merge / rollout note: after git pull, restart the watcher:

  • Via Monitor tool: TaskStop the old watcher task, then re-arm with the same command (now loads new fallback).
  • Via shell: pkill -f watch-tasks-stream.sh — the per-session caller (proactive-loop / startup.sh) will respawn it.

This should also be announced on #bot2bot at merge time so cross-fleet hosts re-sync without dropping DMs in the gap.

Test plan

  • Smoke test: all 3 resolution branches return the expected path
    • no env + no arg → ~/.sutando/workspace/tasks (was <repo>/tasks)
    • SUTANDO_WORKSPACE=/x/ws → /x/ws/tasks
    • positional arg → arg value
  • Stop current Monitor, restart with no env exported, confirm it points at the bridge's actual tasks dir
  • No regression for installs that DO set SUTANDO_WORKSPACE (env branch unchanged)
  • After merge: announce restart-watcher on #bot2bot for cross-fleet alignment

🤖 Generated with Claude Code

@CLAassistant

CLAassistant commented May 17, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM — small, surgical, well-motivated.

Correctness ✅
Verified against src/workspace_default.py:196 resolve_workspace() — that helper returns ~/.sutando/workspace/ when env is unset. The shell fallback now matches exactly. Priority order preserved (positional arg > env > default); the env-set branch is unchanged so happy-path installs aren't touched. $HOME (not ~) is the right form inside double quotes under set -u at line 21 — if $HOME is somehow unset, the script aborts cleanly instead of writing to /.sutando/workspace/tasks.

Conventions ✅
Comment style matches the project's "incident-anchored why" pattern. Both dated incidents + bridge PR cross-refs are present.

Test coverage — partial, defensible
No automated test added. For a 5-line shell fallback with no existing shell-test infrastructure, the 3-branch smoke test in the PR body is a reasonable trade.

Risks

  1. Migration-ordering edge case (narrow). On a host that pulls this commit before any bridge has called resolve_workspace() AND env is unset AND legacy <repo>/tasks/ has unprocessed tasks: the watcher mkdir -p's and polls the new default while legacy tasks still sit at the old path. The bridge's _migrate_from_legacy runs on bridge startup, so any host that also restarts bridges (the standard rollout) closes the gap. Worth a sentence in the rollout note: "after git pull, ensure bridges have been started at least once so legacy migration runs."
  2. Stale watcher process — already addressed in the PR body with pkill -f watch-tasks-stream.sh + #bot2bot announce. 👍

Optional nits (none blocking)

  • The new comment block names the bridges but stops short of pointing at workspace_default.py::resolve_workspace() as the canonical contract. One line like (canonical contract: src/workspace_default.py::resolve_workspace()) would let a future reader trace the source of truth in one hop.
  • Long-term: a tiny src/workspace-default.sh helper that mirrors resolve_workspace() would centralize the next path change. Not for this PR.

Safe to merge once CLA finishes.

@sonichi
sonichi force-pushed the fix/watch-tasks-stream-default-to-canonical-workspace branch from 6dd8bc4 to 77f86e2 Compare May 17, 2026 06:01
…repo>/tasks

When neither $1 nor $SUTANDO_WORKSPACE is set, the watcher fell back to
`<repo>/tasks/` — but the bridges (discord/telegram/dm-result) fall back
to `~/.sutando/workspace/tasks/` via `workspace_default.resolve_workspace()`.

Result: any deployment that doesn't export SUTANDO_WORKSPACE into the
watcher's env has bridges writing one place and the watcher polling another.
Tasks land silently — owner DMs reach the bridge, get archived to results/,
but the agent never sees the `TASK_FILE:` notification.

This affected Chi's MacBook today (~45 min silent gap before manual
restart with the env exported); same bug surfaces on any fresh install
that doesn't put the env var in the watcher's shell.

Switch the fallback to match `workspace_default.default_workspace_dir()` so
the watcher and bridges converge by construction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonichi
sonichi force-pushed the fix/watch-tasks-stream-default-to-canonical-workspace branch from 77f86e2 to 0182fa2 Compare May 17, 2026 06:01

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Verified the fix against main HEAD: src/workspace_default.py exists, bridge imports resolve_workspace() (discord-bridge.py:55), and the function returns ~/.sutando/workspace/ when env is unset. So this PR correctly aligns the watcher's no-env fallback with the bridge's canonical default — convergence-by-construction, not divergence-by-discipline.

One observation for the rollout (already raised in #qw earlier; logging here for the PR record): after this merges, any host with git pull succeeded but Monitor process not restarted is briefly divergent in the opposite direction — old in-memory watcher polls <repo>/tasks/ while bridges (already on new code or restarted) write to ~/.sutando/workspace/tasks/. Same DM-drop failure mode, smaller window. A one-line restart hint in the merge commit body would help.

On Mini's #qw follow-up hardening list (1: startup.sh exports env, 2: health-check probe for watcher_cwd ≠ resolve_workspace(), 3: feedback memory): the health-check probe is the most durable — catches recurrence even when (1) gets reverted or (3) gets forgotten. Worth a follow-up issue if not already filed.

LGTM, no changes requested. Concurring direction.

@sonichi
sonichi merged commit ea23575 into main May 17, 2026
2 checks passed
@sonichi
sonichi deleted the fix/watch-tasks-stream-default-to-canonical-workspace branch May 17, 2026 06:07
qingyun-wu pushed a commit that referenced this pull request May 17, 2026
…on (#780)

Author stays the GitHub-noreply email (CLA-Assistant gate). Committer
becomes per-host so `git log --format='%h %an / %cn %s'` distinguishes
which Sutando fleet host crafted each commit (Mini, MacBook, …).

Opt-in by virtue of having stand-identity.json:
  • Mini  → committer = "Echo Act IV (Mini) <mac-mini@noreply.sutando.local>"
  • MacBook → committer = "<MacBook stand> <macbook@noreply.sutando.local>"
  • OSS user (no stand-identity.json) → no change

Silent fall-through on every "no identity" path: missing file, missing
jq, empty/malformed name or machine. No new env var, no new file, no
hard dep on jq. Smoke-tested all four paths.

Follow-up to today's CLA-Assistant diagnosis (#779, #777): root cause
of the silent CLA-pending was Mini's commit author being
`Chi <wangchi@Chis-Mac-mini.local>` (OS-default hostname email) while
MacBook's was `Chi Wang <4250911+sonichi@users.noreply.github.com>`
(GitHub privacy noreply, resolvable). Author is now uniform via
repo-local config; committer carries the per-host signal that author
used to (badly) carry.

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
…repo>/tasks (sonichi#779)

When neither $1 nor $SUTANDO_WORKSPACE is set, the watcher fell back to
`<repo>/tasks/` — but the bridges (discord/telegram/dm-result) fall back
to `~/.sutando/workspace/tasks/` via `workspace_default.resolve_workspace()`.

Result: any deployment that doesn't export SUTANDO_WORKSPACE into the
watcher's env has bridges writing one place and the watcher polling another.
Tasks land silently — owner DMs reach the bridge, get archived to results/,
but the agent never sees the `TASK_FILE:` notification.

This affected Chi's MacBook today (~45 min silent gap before manual
restart with the env exported); same bug surfaces on any fresh install
that doesn't put the env var in the watcher's shell.

Switch the fallback to match `workspace_default.default_workspace_dir()` so
the watcher and bridges converge by construction.

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
…on (sonichi#780)

Author stays the GitHub-noreply email (CLA-Assistant gate). Committer
becomes per-host so `git log --format='%h %an / %cn %s'` distinguishes
which Sutando fleet host crafted each commit (Mini, MacBook, …).

Opt-in by virtue of having stand-identity.json:
  • Mini  → committer = "Echo Act IV (Mini) <mac-mini@noreply.sutando.local>"
  • MacBook → committer = "<MacBook stand> <macbook@noreply.sutando.local>"
  • OSS user (no stand-identity.json) → no change

Silent fall-through on every "no identity" path: missing file, missing
jq, empty/malformed name or machine. No new env var, no new file, no
hard dep on jq. Smoke-tested all four paths.

Follow-up to today's CLA-Assistant diagnosis (sonichi#779, sonichi#777): root cause
of the silent CLA-pending was Mini's commit author being
`Chi <wangchi@Chis-Mac-mini.local>` (OS-default hostname email) while
MacBook's was `Chi Wang <4250911+sonichi@users.noreply.github.com>`
(GitHub privacy noreply, resolvable). Author is now uniform via
repo-local config; committer carries the per-host signal that author
used to (badly) carry.

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.

3 participants