Skip to content

fix(startup): tee stdout+stderr to /tmp self-log (Lucy's Bug #5) - #1484

Merged
qingyun-wu merged 1 commit into
staging-workspace-revampfrom
fix/startup-self-log-tmp
Jun 6, 2026
Merged

qingyun-wu merged 1 commit into
staging-workspace-revampfrom
fix/startup-self-log-tmp

Conversation

@qingyun-wu

Copy link
Copy Markdown
Collaborator

Summary

Closes Bug #5 of 5 from Lucy's Maddy v0.8 migration report (2026-06-06 #design). Last of her five 🎉

Lucy ran nohup bash src/startup.sh > \$SUTANDO_WORKSPACE/logs/startup-<ts>.log 2>&1 & over SSH. Mid-run the v0.8 auto-migration `rm -rf`'d the legacy workspace dir her redirect was pointing at → log vanished mid-write, postmortem impossible.

Read-through confirms there's no startup.sh-internal leak — service logs all land in `$LOGS_DIR=$WORKSPACE/logs` resolved AFTER line 184's `unset SUTANDO_WORKSPACE` + fresh config resolve. Lucy concurred: "There's no startup.sh-internal leak. The lost log was my own invocation, not the script. Ship your fallback — tee startup.sh's stdout+stderr to /tmp/sutando-startup-.log at the very top. That's exactly the right fix here: it guarantees a recoverable trace regardless of how it's invoked."

Fix

At the very top of `src/startup.sh` (BEFORE any other output, BEFORE the v0.8 auto-migration block), tee stdout + stderr to `/tmp/sutando-startup--.log`:

```bash
if [ "${SUTANDO_STARTUP_NO_LOG:-0}" != "1" ]; then
_STARTUP_LOG="/tmp/sutando-startup-$(date -u +%Y%m%dT%H%M%SZ)-$$.log"
echo "📓 startup log → $_STARTUP_LOG" >&2
exec > >(tee -a "$_STARTUP_LOG") 2> >(tee -a "$_STARTUP_LOG" >&2)
fi
```

  • PID suffix prevents collisions when the operator retries while a hung previous invocation is still running.
  • Announce line goes to stderr immediately so the operator knows where to look if their own redirect gets eaten.
  • Opt-out: `SUTANDO_STARTUP_NO_LOG=1` (e.g. test harnesses with their own output capture).

Tests

`tests/startup-self-log-tmp.test.sh`:

  • 6 structural: opt-out env var honored, /tmp path, `exec > >(tee -a ...)` for stdout AND `2> >(tee -a ...)` for stderr, announce-to-stderr, tee block BEFORE the auto-migration block, PID in filename.
  • 3 E2E: extracted block writes both stdout AND stderr to the captured /tmp log while preserving pass-through to real fds; opt-out env var actually skips the tee; announce line goes to stderr only.

ALL TESTS PASS.

Diff stats

2 files changed, +148 (20 in startup.sh, 128 test).

Related

v0.3.0 milestone table

Milestone Status
M0 (in-repo workspace default) ✓ merged
M1 (auto-recovery / runtime safeguards) ✓ merged
M2 (per-runtime CLAUDE_CONFIG_DIR) ✓ merged
M3 (v0.3.0 cutover) tracked in #1472

Test plan

  • Read the new tee block at top of startup.sh — confirm it runs BEFORE any other output
  • Run `bash tests/startup-self-log-tmp.test.sh` locally — confirm ALL TESTS PASS
  • On a fresh boot, verify `📓 startup log → /tmp/sutando-startup-*.log` line appears on stderr
  • Cat that /tmp log after startup completes — should contain the full output

🤖 Generated with Claude Code

…0.8 auto-migration rm -rf (Lucy's Bug #5)

Lucy's Maddy v0.8 migration report (2026-06-06 #design): she ran
`nohup bash src/startup.sh > $SUTANDO_WORKSPACE/logs/startup-<ts>.log 2>&1 &`
over SSH. Mid-run the v0.8 auto-migration `rm -rf`'d the legacy
workspace dir her redirect was pointing at → log vanished
mid-write, postmortem impossible.

Read-through confirms there's no startup.sh-internal leak — service
logs all land in $LOGS_DIR=$WORKSPACE/logs resolved AFTER line 184's
`unset SUTANDO_WORKSPACE` + fresh config resolve. Lucy 2026-06-06:
"There's no startup.sh-internal leak. The lost log was my own
invocation, not the script. … ship your fallback — tee startup.sh's
stdout+stderr to /tmp/sutando-startup-<ts>.log at the very top. That's
exactly the right fix here: it guarantees a recoverable trace
regardless of how it's invoked."

Belt-and-suspenders: at the very top of startup.sh (BEFORE the v0.8
auto-migration block runs), tee stdout + stderr to
/tmp/sutando-startup-<UTC-ts>-<PID>.log. The PID suffix prevents
collisions when an operator retries while a hung previous invocation
is still running. The log path is announced to stderr immediately so
the operator knows where to look if their own redirect gets eaten.

Opt out with SUTANDO_STARTUP_NO_LOG=1 (e.g. test harnesses with their
own output capture).

Tests: tests/startup-self-log-tmp.test.sh
- 6 structural assertions: opt-out env var, /tmp path, exec+tee pattern
  for stdout AND stderr, announce-to-stderr, tee block BEFORE the
  auto-migration block, PID in filename for concurrent-startup isolation.
- 3 E2E: extracted block writes both stdout AND stderr to the captured
  /tmp log while preserving pass-through to real fds; opt-out env var
  actually skips the tee; announce line goes to stderr only.

Bug #5 of 5 from Lucy's Maddy migration report — closes the sweep.
Lucy 2026-06-06: "That closes all 5 🎉 — great sweep."

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. exec-with-process-substitution tee is the right pattern — both stdout and stderr captured, transparent to the rest of the script. Critical ordering: tee runs before auto-migration (Test 5 verifies this explicitly, which is exactly right — an early migration abort would otherwise be invisible). Timestamp+PID gives uniqueness for concurrent invocations. SUTANDO_STARTUP_NO_LOG=1 opt-out is clean. Test suite covers all 6 structural invariants. Stand: Echo Act IV Pro

@qingyun-wu
qingyun-wu merged commit 4115b3a into staging-workspace-revamp Jun 6, 2026
4 checks passed
@qingyun-wu
qingyun-wu deleted the fix/startup-self-log-tmp branch June 6, 2026 04:01
qingyun-wu added a commit that referenced this pull request Jun 6, 2026
…rator checklist + workspace-sync node_modules

Per Chi's PR #1472 comments (#design 2026-06-06):

**CHANGELOG.md v0.3.0 additions:**
- Fixed → Migration subsection: Lucy's Maddy v0.8 5-bug sweep (#1475 #1478 #1482 #1483 #1484)
- Fixed → Cross-platform: #1476 (uname -s branch for preflight byte count)
- Fixed → Security: #1452 #1462 #1466 (execSync → execFileSync sweep)
- Added: #1467 (bridges inject skill instructions into owner task files)
- Workspace contract rollup → M1: also reference #1473 (pre-flight scan + per-file progress reporter)

**KNOWN_ISSUES.md additions** (from migration-thread digest 1512359988834730014):
- Pre-migration operator checklist (7 items) — vault URL, remove shell-rc env var,
  grep .git/info/exclude, confirm wsId, stagger hosts, restart services,
  post-migration sanity check
- Identity-collision rule — exactly one host at a time can hold a Discord token
  (also applies to Twilio + other per-identity creds)

**docs/workspace-sync.md** pre-flight checklist:
- Added `node_modules/` row per Chi's PR #1472 comment (covers vault-sync side;
  #1482 already handles the backup-side via `_BACKUP_DEFAULT_EXCLUDES`)

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