Skip to content

fix(notifications): report why the Telegram daemon child exits before readiness (#3761) - #3844

Closed
yazzang-homelab wants to merge 1 commit into
Yeachan-Heo:devfrom
yazzang-homelab:fix/3761-daemon-exit-diagnostics
Closed

fix(notifications): report why the Telegram daemon child exits before readiness (#3761)#3844
yazzang-homelab wants to merge 1 commit into
Yeachan-Heo:devfrom
yazzang-homelab:fix/3761-daemon-exit-diagnostics

Conversation

@yazzang-homelab

@yazzang-homelab yazzang-homelab commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What

Every pre-readiness exit of the detached Telegram notification daemon child now writes one credential-free line explaining why it exited, on the child's own stderr — the fd the launcher already redirects into notifications/daemon.log — plus a notice once ownership reaches ready.

Covered exits:

exit previous diagnostic
owner pid from --owner-id is not alive logger.warn only (never flushed by a startup-exiting child)
config.yml does not enable a complete Telegram provider none
notification settings cannot be loaded none (throw only)
bot token missing or blank (TelegramNotificationDaemon.run) none
any ownership-admission refusal in renewDaemonHeartbeat none (bare false), except two logger.warn paths
ownership reached ready none

renewDaemonHeartbeat gains an optional onRefusal(reason) and reports the exact refusing condition (contended/unusable transition lock, which state field mismatched, lock rebind failure, state write failure, sidecar proof failure, ready-publication failure). The admission guard is unchanged and stays authoritativedescribeDaemonHeartbeatRefusal only names the first failing condition for the message, and falls back to unspecified mismatch.

Secret handling: the owner id doubles as the acquisition secret, so diagnostics carry only the pid derived from it, never the id; token-shaped values are redacted by sanitizeDaemonDiagnostic. The stderr sink is wired only at the notify daemon-internal process boundary, so the embeddable daemon class never writes to a host process's stderr (asserted by a test).

DAEMON_GENERATION 51 → 52 (rebased onto dev again after #3828 claimed 51; #3834 had claimed 50), contract documented, generation pin test updated, manifest regenerated.

Why

Refs #3761 — the diagnostic slice, which needs no Telegram credentials. Reported symptom: notify daemon-internal "exits immediately with status 0 and no output on either stream", notifications/daemon.log 0 bytes, and (follow-up comment) run() returning in 4 ms from renewDaemonHeartbeat with no log line, which made the whole activation failure unobservable from outside. The issue asks for exactly this: "even if the ownership refusal is correct, it should not be silent. A one-line reason on the child's stderr (which the launcher can capture into notifications/daemon.log) would have turned this whole investigation into one command."

logger is the wrong channel here — a child that exits during startup never flushes it. This does not claim to fix the activation failure itself, does not auto-close the issue, and is independent of the #3834 symlink hypothesis and of the owner-required live reproduction gate. It makes the remaining gate cheap to satisfy: the required "child exit/startup diagnostics" and "ownership transition from provisional to ready" evidence now falls out of notifications/daemon.log.

Slice ledger for #3761: #3763 (merged, wording) and #3768 (merged, recovery) left the child-side diagnostic bullet of the issue's "Tests worth adding" unclaimed; this is that bullet.

Testing

  • bun test packages/coding-agent/test/issue-3761-daemon-startup-diagnostics.test.ts — 12 pass. Includes a real subprocess whose stdout/stderr are an fd on notifications/daemon.log (the exact production wiring), asserting the reason is readable from the file after the child is gone and that the owner-id secret is absent.
  • bun test issue-3761-daemon-startup-diagnostics.test.ts notifications-telegram-daemon.test.ts notify-setup.test.ts — 620 pass, 0 fail on this head (includes the generation-52 pin).
  • bun scripts/telegram-daemon-generation-guard.ts --validate-current-tree → OK; bun scripts/telegram-daemon-generation-guard.ts 844002de8990b46ee43452759938de1820b3cce8 3b4698e727e9f12410fadd8ba9f26c908e22b1c0v43 required generation bump verified; bun test scripts/telegram-daemon-generation-guard.test.ts — 48 pass.
  • bun --cwd=packages/coding-agent run check — biome clean, tsc --noEmit clean.
  • bun test packages/coding-agent/test/sdk-operation-inventory.test.ts — 17 pass on this head.
  • Not run: live Linux Telegram bot activation and inbound round trip (no maintainer-controlled credentials). No behavior in this PR depends on a live bot.

GJC verdict

gajae.pr-review-verdict.v1 needs-human sha256:3b4698e727e9f12410fadd8ba9f26c908e22b1c0 reviewer:human evidence:local bun test issue-3761-daemon-startup-diagnostics + notifications-telegram-daemon + notify-setup (620 pass) + generation guard 844002de..3b4698e7

No independent architect/critic review was performed; author self-review only. Needs maintainer review.


  • Target branch is dev
  • bun check passes
  • Tested locally
  • CHANGELOG updated (if user-facing)
  • Verdict above matches the exact PR head, not an earlier commit

@Yeachan-Heo

Copy link
Copy Markdown
Owner

Maintainer triage (external review): the change itself is sound and CI is green on the exact head (f9a7532c), but #3834 — the symlinked-notifications activation slice of the same issue — merged first and claimed DAEMON_GENERATION 50. This PR now conflicts with dev on:

  • packages/coding-agent/src/sdk/bus/telegram-daemon-contract.ts (both set DAEMON_GENERATION = 50)
  • packages/coding-agent/test/notifications-telegram-daemon.test.ts (generation pin)

(CHANGELOG and the manifest auto-merge, but the guard requires a bump per committed range.)

Please rebase onto the current dev and:

  1. bump DAEMON_GENERATION 50 → 51 and document generation 51 in the contract comment;
  2. update the pin test name/comment and expect(DAEMON_GENERATION).toBe(51);
  3. run bun scripts/telegram-daemon-generation-guard.ts --write-manifest after the edits, then --validate-current-tree and bun test scripts/telegram-daemon-generation-guard.test.ts;
  4. re-run the focused suites (issue-3761-daemon-startup-diagnostics.test.ts, notifications-telegram-daemon.test.ts, notify-setup.test.ts) and bun --cwd=packages/coding-agent run check.

The diagnostics implementation itself was verified against dev source: describeDaemonHeartbeatRefusal mirrors the 14-condition renewDaemonHeartbeat admission guard 1:1 in order, the owner-id secret is never written (pid-only reporting), and the stderr sink is wired only at the notify daemon-internal process boundary. No functional changes requested — this is a rebase + generation re-claim only.

@Yeachan-Heo Yeachan-Heo 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.

Verdict: REQUEST_CHANGES — one blocker; the implementation itself is sound.

Blocker — DAEMON_GENERATION 50 is already claimed on dev by #3834 (merged 2026-08-05T06:31:15Z); this PR cannot merge into current dev as-is.

Verified independently (read-only, exact head f9a7532c):

  • GitHub reports mergeable: CONFLICTING, mergeStateStatus: DIRTY for this head against dev.
  • Current dev telegram-daemon-contract.ts already contains export const DAEMON_GENERATION = 50; (claimed by #3834, the symlinked-notifications activation slice of the same issue). This PR bumps 49 → 50 on its own base, so both telegram-daemon-contract.ts and the generation pin in test/notifications-telegram-daemon.test.ts conflict with dev.
  • The telegram-daemon-generation-guard requires a strictly higher DAEMON_GENERATION per committed range whenever a protected Telegram lifecycle declaration changes. run, renewDaemonHeartbeat, and runDaemonInternal are all protected and all change here. Rebased onto current dev, the range would be 50 → 50, so the guard throws (protected Telegram lifecycle change requires a strictly higher DAEMON_GENERATION). CI is green on the exact head only because the PR's own base was generation 49 (50 > 49).

Required fix (mechanical — no functional change requested):

  1. Rebase onto current dev.
  2. Bump DAEMON_GENERATION 50 → 51 and document generation 51 in the contract comment.
  3. Update the pin test name/comment and expect(DAEMON_GENERATION).toBe(51).
  4. Regenerate the manifest (bun scripts/telegram-daemon-generation-guard.ts --write-manifest) so it byte-matches the rebased head tree, then --validate-current-tree and run bun test scripts/telegram-daemon-generation-guard.test.ts.
  5. Re-run the focused suites (issue-3761-daemon-startup-diagnostics.test.ts, notifications-telegram-daemon.test.ts, notify-setup.test.ts) and bun --cwd=packages/coding-agent run check.

What I verified and found sound (no other changes requested):

  • describeDaemonHeartbeatRefusal mirrors the 14-condition renewDaemonHeartbeat admission guard 1:1 in order — every return false site in the guard has a matching named refusal, and canBindProvisionalPid is computed identically. The guard itself is unchanged and stays authoritative; renewDaemonHeartbeat's boolean return contract is untouched (onRefusal is an optional addition, and the daemon is its only caller).
  • Security/privacy: the owner id (which doubles as the acquisition secret) is never written — only its pid is reported via ownerPidFromOwnerId. The bot token never appears in any diagnostic line; the existing sanitizeDiagnostic (exact + token-shape redaction) and the new sanitizeDaemonDiagnostic (\d{6,}:[A-Za-z0-9_-]{20,} redaction) are defense-in-depth on the durable line. daemon.log is opened 0o600 inside a 0o700 directory.
  • Stderr boundary: the stderr sink is wired only at the notify daemon-internal process boundary (the launcher already redirects stdout/stderr into notifications/daemon.log in defaultDaemonSpawn). The embeddable TelegramNotificationDaemon never writes to a host process's stderr without an explicit sink, and the sink call is try/caught so a diagnostic can never break the exit path it describes.
  • Contract boundaries: RunDaemonInternalDeps.diagnostic, TelegramDaemonOptions.diagnostic, and renewDaemonHeartbeat.onRefusal are all optional; wire protocol version stays 3.
  • Generated artifacts: telegram-daemon-generation-manifest.json byte-matches the PR head tree (guard --validate-current-tree passes); the CHANGELOG entry is accurate and under [Unreleased].
  • CI on the exact head: all runs green — Telegram daemon generation guard, gjc-state-gates, check:coding-agent, root-check, the three touched test suites, and the new issue-3761-daemon-startup-diagnostics suite.
  • Local verification: 12/12 new diagnostics tests pass (including the real-subprocess test asserting the line lands in daemon.log without the acquisition secret); the modified notify-setup and notifications-telegram-daemon cases pass; no dangling references to the removed recordDaemonCompatibilityDiagnostic.

@yazzang-homelab
yazzang-homelab force-pushed the fix/3761-daemon-exit-diagnostics branch from f9a7532 to 489ba88 Compare August 5, 2026 06:46
@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

Rebased onto dev @ 0fdbb5e8 (post-#3834). New head: 489ba88d. No functional change — generation re-claim only.

  1. DAEMON_GENERATION 50 → 51; contract comment now documents fix(notifications): resolve intermediate notifications dir only for exact unlink #3834's generation 50 (symlink resolution) and this slice as generation 51.
  2. Pin test renamed to keeps wire protocol 3 through generation 51 daemon startup diagnostics, comment carries both 50 and 51, expect(DAEMON_GENERATION).toBe(51).
  3. bun scripts/telegram-daemon-generation-guard.ts --write-manifest re-run and amended into the commit; --validate-current-tree → exit 0; bun scripts/telegram-daemon-generation-guard.ts 0fdbb5e800445e4fb3acccd1663b0fba81dcca82 489ba88dc20b1a060749c57283ad5be2ca5c4037v43 required generation bump verified; bun test scripts/telegram-daemon-generation-guard.test.ts — 48 pass, 0 fail.
  4. bun test issue-3761-daemon-startup-diagnostics.test.ts notifications-telegram-daemon.test.ts notify-setup.test.ts — 609 pass, 0 fail. bun --cwd=packages/coding-agent run check — biome clean (2519 files), tsc --noEmit clean.

Re-verified the admission mirror against the new base: dev 5938de5..0fdbb5e touched only notification-service.ts and telegram-daemon-contract.ts under src/sdk/bus/, so renewDaemonHeartbeat's 14-condition guard is byte-identical to what describeDaemonHeartbeatRefusal mirrors. telegram-daemon.ts merged with no conflict.

Verdict in the PR body is rebound to the exact new head:

gajae.pr-review-verdict.v1 needs-human sha256:489ba88dc20b1a060749c57283ad5be2ca5c4037 reviewer:human evidence:local bun test issue-3761-daemon-startup-diagnostics + notifications-telegram-daemon + notify-setup (609 pass) + generation guard 0fdbb5e8..489ba88d

@yazzang-homelab
yazzang-homelab force-pushed the fix/3761-daemon-exit-diagnostics branch from 489ba88 to 1329a0f Compare August 5, 2026 07:19
@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

dev advanced to 0825a4208 (#3753) while checks were finishing, so the branch was rebased once more. New head: 1329a0f4. That rebase was CHANGELOG-only (auto-merged, no conflict); generation 51 and the diagnostics code are unchanged from 489ba88d.

Re-verified on the new base:

  • bun scripts/telegram-daemon-generation-guard.ts --validate-current-tree → exit 0; bun scripts/telegram-daemon-generation-guard.ts 0825a42083400c8867b2c7bcc89b53e5a953360b 1329a0f40764567a2129919727d57cfe98c9d864v43 required generation bump verified; bun test scripts/telegram-daemon-generation-guard.test.ts — 48 pass, 0 fail.
  • bun test issue-3761-daemon-startup-diagnostics.test.ts notifications-telegram-daemon.test.ts notify-setup.test.ts — 609 pass, 0 fail.
  • bun --cwd=packages/coding-agent run check — biome clean (2519 files), tsc --noEmit clean.
  • CI on the previous head 489ba88d was fully green (26 pass, 5 skipping, 0 pending) with the identical tree modulo the Slack CHANGELOG line.

Verdict rebound to the exact head:

gajae.pr-review-verdict.v1 needs-human sha256:1329a0f40764567a2129919727d57cfe98c9d864 reviewer:human evidence:local bun test issue-3761-daemon-startup-diagnostics + notifications-telegram-daemon + notify-setup (609 pass) + generation guard 0825a420..1329a0f4

@yazzang-homelab
yazzang-homelab force-pushed the fix/3761-daemon-exit-diagnostics branch 2 times, most recently from 45c155c to 3b4698e Compare August 5, 2026 08:49
@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

dev advanced to 844002de and #3828 (durable topic archive authority) claimed generation 51 in the meantime, so this branch conflicted again. Rebased onto 844002de; new head 3b4698e7.

Generation re-claim plus three real code merges this time (#3828 touched the same call sites), all union merges with no behavior dropped from either side:

  1. DAEMON_GENERATION 51 → 52; the contract comment keeps fix(telegram): harden durable topic archive authority #3828's generation-51 paragraph and appends the generation-52 diagnostics sentence.
  2. telegram-daemon-cli.ts: loadInstallationHostId / diagnostic deps coexist; the unconfigured-provider branch now reports the diagnostic and returns before the shared topic-registry authority is constructed; the daemon receives topicRegistryAuthority, installationHostId and diagnostic.
  3. telegram-daemon.ts: both option blocks kept; the blank-bot-token guard reports its reason and returns, and fix(telegram): harden durable topic archive authority #3828's validation-forum assertions still run for a valid token.
  4. Pin test renamed to keeps wire protocol 3 through generation 52 daemon startup diagnostics, comment carries 50, 51 and 52, expect(DAEMON_GENERATION).toBe(52). Manifest regenerated and amended.

Re-verified on the new base (844002de):

  • bun --cwd=packages/coding-agent run check — biome clean, tsc --noEmit clean.
  • bun test issue-3761-daemon-startup-diagnostics.test.ts notifications-telegram-daemon.test.ts notify-setup.test.ts — 620 pass, 0 fail.
  • bun scripts/telegram-daemon-generation-guard.ts --validate-current-tree → exit 0; bun scripts/telegram-daemon-generation-guard.ts 844002de8990b46ee43452759938de1820b3cce8 3b4698e727e9f12410fadd8ba9f26c908e22b1c0v43 required generation bump verified; bun test scripts/telegram-daemon-generation-guard.test.ts — 48 pass.
  • GitHub now reports mergeable: true against dev.

The PR body verdict line is re-pinned to the exact head 3b4698e7.

One process note rather than a code request: this is the third generation collision on the same issue (#3834 took 50, #3828 took 51). The guard's per-range bump requirement makes the counter a serialized resource, so any daemon-touching PR that merges first forces a rebase here even when the diagnostics change itself is untouched. This slice is behind two others by construction; if further daemon work is queued ahead of it, I will keep re-claiming, but merging it before the next daemon PR would stop the treadmill.

@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

Re-review request — the REQUEST_CHANGES blocker is resolved at the current head and no rebase is pending.

The blocker was the generation-50 collision with #3834. That is settled: DAEMON_GENERATION is 52 here, dev is still 51, and the generation-52 paragraph is in the contract comment.

Re-verified just now against dev at 6c1c8a47, read-only, exact head 3b4698e7:

  • dev advanced 6 commits past this PR's base 844002de, and none of them touch a daemon-protected surface (telegram-daemon.ts, telegram-daemon-contract.ts, telegram-daemon-cli.ts, telegram-daemon-generation-manifest.json). The generation counter did not move, so there is no fourth re-claim to do.
  • bun scripts/telegram-daemon-generation-guard.ts --validate-current-tree → exit 0 (manifest byte-matches the head tree).
  • bun test scripts/telegram-daemon-generation-guard.test.ts packages/coding-agent/test/issue-3761-daemon-startup-diagnostics.test.ts packages/coding-agent/test/notifications-telegram-daemon.test.ts packages/coding-agent/test/notify-setup.test.ts — 668 pass, 0 fail.
  • Pin test asserts expect(DAEMON_GENERATION).toBe(52).
  • GitHub reports mergeable: true, mergeStateStatus: CLEAN; all 30 check runs on 3b4698e7 are success or skipped.

Every item in the required fix list is satisfied at this head; the only thing still blocking is the CHANGES_REQUESTED review recorded against the older head f9a7532c. I am not force-pushing or re-rebasing to clear it, since neither would.

The treadmill note from the previous comment still stands: this slice has now re-claimed the generation counter three times (#3834 took 50, #3828 took 51). It is currently the only daemon-touching branch that is green and conflict-free, so merging it before the next daemon PR ends the cycle.

yazzang-homelab added a commit to yazzang-homelab/gajae-code that referenced this pull request Aug 5, 2026
Generation 52 is already claimed by the pre-readiness daemon-child exit
diagnostics slice (Yeachan-Heo#3844), which is ahead of this branch in review, so
this slice claims 53 instead of guaranteeing a guard failure for
whichever PR lands second.

Lore-id: 5a3c91d7
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: telegram-daemon generation guard against current dev
Yeachan-Heo pushed a commit that referenced this pull request Aug 6, 2026
* fix(ask): show multi-select state on remote asks

The ask tool owns the multi-select loop and re-issues one remote request
per toggle, but AskAnswerRequest carried no selection state: Telegram
posted an identical prompt each time, so nothing showed that option 1 had
been picked. Selection rendering existed only for durable workflow gates,
which take a different presentation path.

AskAnswerRequest now carries multi plus the selected option labels, the
interactive presentation forwards them so the bus publishes
selectedOptionIndices and the "(N selected)" prefix, and a presentation
that brings its own navigation control keeps it instead of the gate's
synthesized Done. Option renumbering now looks past a leading selection
marker so pre-numbered deep-interview options render as "1. checked
Alpha" rather than "1. checked 1. Alpha".

Lore-id: b48e2d17
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: arbiter publication after a toggle, ask-tool remote requests across a toggle, daemon rendering of pre-numbered options
Not-tested: live Telegram client rendering of the checkbox glyphs

* test(notifications): pin the daemon generation to 53

Generation 52 is already claimed by the pre-readiness daemon-child exit
diagnostics slice (#3844), which is ahead of this branch in review, so
this slice claims 53 instead of guaranteeing a guard failure for
whichever PR lands second.

Lore-id: 5a3c91d7
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: telegram-daemon generation guard against current dev

* test(notifications): stop the shutdown-admission join from racing wall-clock

`effects.join` counts its deadline from the call, and the held-state race
plus its assertion run inside that window before the persistence barrier
is released. A 100 ms budget therefore reports `false` for a barrier that
did hold as soon as the runner is loaded: shard-1-of-8 failed exactly this
assertion on head 6d1c1b6, while the same shard passed on 160159c
where every source file on that path is identical.

The held half of the contract is proven by the race, not by the deadline,
so the deadline only has to outlast the released work. The daemon's own
shutdown join uses 1s; 5s here keeps the assertion load-bearing (a barrier
that never settles still fails) without timing out on a slow runner.

Lore-id: 9b2d5e14
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: assertion still fails when the released work never settles; suite green pinned to one core

---------

Co-authored-by: yazzang-homelab <yazzang-homelab@users.noreply.github.com>
@yazzang-homelab
yazzang-homelab force-pushed the fix/3761-daemon-exit-diagnostics branch from 3b4698e to f51d455 Compare August 6, 2026 01:45
@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

Rebased onto current dev (b621997ee) — new head f51d455d5. The generation collision this PR was blocked on is settled in the other direction: this slice now claims 54.

What happened: the blocker review asked for a generation that does not collide on dev. #3899 landed first and published 53, and its contract comment had reserved 52 for this slice. 52 is therefore unreachable now (the guard requires strictly higher than base), and it was never served by any daemon. Rather than leave a comment that claims an unpublished generation, the contract and both pins now record it plainly: 52 was reserved for this slice and left unpublished, 53 is the ask-tool multi-select rendering, 54 is this slice.

The rebase was not clean and I did not let git guess:

  • telegram-daemon-contract.ts — both sides rewrote the trailing generation paragraph. Resolved by hand into one paragraph covering 52 (unpublished), 53, and 54.
  • notifications-telegram-daemon.test.ts — both sides had replaced the same wire-protocol test's title and tail, so git produced two hunks of one test. The first automatic resolution left an orphaned comment block and a duplicated expect outside any test; I aborted that rebase, redid it, and took dev's test structure with the title and history comment extended to 54. Verified the block is syntactically whole, with exactly one expect(DAEMON_GENERATION) in it.
  • scripts/telegram-daemon-generation-manifest.json — regenerated with --write-manifest (three digests moved: DAEMON_GENERATION, renewDaemonHeartbeat, run).
  • notifications-topic-registry.test.ts — its publishes generation N pin follows to 54.

Re-verified on the new base:

  • bun scripts/telegram-daemon-generation-guard.ts (base b621997ee, head f51d455d5) → v43 required generation bump verified.
  • bun test packages/coding-agent/test/notifications-telegram-daemon.test.ts packages/coding-agent/test/notifications-topic-registry.test.ts → 608 pass, 0 fail.
  • bun test packages/coding-agent/test/issue-3761-daemon-startup-diagnostics.test.ts packages/coding-agent/test/notify-setup.test.ts → 68 pass, 0 fail.
  • tsc --noEmit -p packages/coding-agent/tsconfig.json clean; biome check clean on the touched files.

The diagnostics code itself is unchanged from the head you reviewed as sound; only the generation claim, the two pins, the contract paragraph, and the manifest digests moved. Re-review requested — no rebase is pending and dev is at b621997ee as of this push.

@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

The blocker is resolved on the current head — this is now rebased onto dev and the generation collision is gone. Re-requesting review.

Exact head f51d455d5322ab94d93be878fe35520622aaa5ff:

  • Contains current dev (b621997ee792bedf1c24870153d0c5cb09561a5a); GitHub reports MERGEABLE / CLEAN.
  • DAEMON_GENERATION is 54; dev is at 53. Strictly higher per committed range, so the guard admits the protected run / renewDaemonHeartbeat / runDaemonInternal changes. The pin test asserts 54 and the contract comment documents generation 54.
  • Manifest regenerated: bun scripts/telegram-daemon-generation-guard.ts --validate-current-tree exits 0 against the head tree.
  • bun test packages/coding-agent/test/issue-3761-daemon-startup-diagnostics.test.ts packages/coding-agent/test/notify-setup.test.ts — 68 pass, 0 fail, 317 assertions.
  • CI on this head: 27 checks green, 5 skipped, 0 failing.

No functional change from the reviewed revision — the rebase carried the generation bump and the manifest regeneration only.

@yazzang-homelab
yazzang-homelab force-pushed the fix/3761-daemon-exit-diagnostics branch from f51d455 to 3b65a82 Compare August 6, 2026 09:33
@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

Rebased onto current dev (1f735b7bc6bb1fc62c782496f2e2ab9b02adec2a). Supersedes the evidence in my previous comment, which cited a stale dev.

New exact head 3b65a82cb2705b517ac70fb5334d87312a778a86:

  • Contains current dev; MERGEABLE / CLEAN. 27 checks green, 5 skipped, 0 failing.
  • DAEMON_GENERATION 54 vs 53 on dev — still strictly higher per committed range, so the original blocker stays resolved after the rebase.
  • bun scripts/telegram-daemon-generation-guard.ts --validate-current-tree exits 0 against the rebased tree (manifest byte-matches; no regeneration was needed).
  • bun test packages/coding-agent/test/issue-3761-daemon-startup-diagnostics.test.ts packages/coding-agent/test/notify-setup.test.ts scripts/telegram-daemon-generation-guard.test.ts — 116 pass, 0 fail, 705 assertions.
  • Rebase was content-free: one commit replayed with no conflicts.

@Yeachan-Heo ready for re-review against this head.

@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

DAEMON_GENERATION 충돌 주의 — #3891도 53 → 54로 올린다. 먼저 머지되는 쪽이 54를 가져가고, 나머지는 telegram-daemon-generation-guard에서 protected Telegram lifecycle change requires a strictly higher DAEMON_GENERATION로 떨어진다. @Yeachan-Heo 가 이 PR에서 #3834 대 50으로 잡았던 것과 같은 충돌이다.

지금 한쪽을 55로 올려두는 건 의미가 없다 — dev가 움직이면 다시 어긋난다. 머지 순서만 정해주면 두 번째 것이 리베이스하면서 55로 올리면 된다. #3891이 먼저 가면 내가 이쪽을 55로 올리겠다.

A detached `notify daemon-internal` child exited with status 0, an empty
notifications/daemon.log, and no reachable diagnostic on every startup
refusal, so a failed activation was indistinguishable from a daemon that
was never spawned (Yeachan-Heo#3761). `logger` is the wrong channel here: a child
that exits during startup never flushes it. The launcher already
redirects the child's stdout/stderr into notifications/daemon.log, so the
daemon-internal entrypoint now passes that stderr as a diagnostic sink
and every pre-readiness exit writes one credential-free line to it, plus
a notice once ownership reaches ready. renewDaemonHeartbeat reports the
exact refusing condition instead of a bare false; its admission guard is
unchanged and stays authoritative.

Lore-id: 3761-diag
Constraint: the owner id doubles as the acquisition secret -- diagnostics carry only its pid
Constraint: the embeddable daemon class must not write to a host process's stderr
Rejected: logger-only diagnostics | a startup-exiting child never flushes that sink
Rejected: a new telegram-daemon.diagnostics.json writer | racy, and the log fd is already wired
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test packages/coding-agent/test/issue-3761-daemon-startup-diagnostics.test.ts (12 pass, includes a real subprocess whose stderr is redirected into daemon.log)
Tested: notifications-telegram-daemon (514 pass), notify-setup, generation guard base..head
Not-tested: live Linux Telegram bot activation and inbound round trip (no maintainer credentials)
@Yeachan-Heo
Yeachan-Heo force-pushed the fix/3761-daemon-exit-diagnostics branch from 3b65a82 to b2be479 Compare August 6, 2026 11:27
@Yeachan-Heo

Copy link
Copy Markdown
Owner

Closing this contribution under maintainer direction. Do not open further PRs or issues in this repository without explicit maintainer approval.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo Yeachan-Heo closed this Aug 6, 2026
@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

현재 dev(0.12.13/14/15 릴리스 포함)로 리베이스하고 CHANGELOG 항목 위치를 고쳤다. 새 head 5341f44f1edb82975501c8df233d66ad6cbcb24b.

앞선 head에서 이 PR의 항목이 이미 배포된 ## [0.12.12] - 2026-08-05 섹션 안(59행)에 들어가 있었다. merge=union이 리베이스에서 항목을 새 버전 헤딩 아래로 조용히 옮긴 결과다(#3929). 내 PR도 예외가 아니었다 — 열린 PR 8건이 같은 상태였고 그중 하나가 이거였다.

이제 ## [Unreleased] > ### Fixed 아래에 있다:

3| ## [Unreleased]
5| ### Fixed
7| - Every pre-readiness exit of the detached Telegram notification daemon child now records ...

## [0.12.12] 이하 릴리스 섹션에는 잔여 없음(grep 0건). CHANGELOG 313,259 bytes로 온전하다.

DAEMON_GENERATION은 54, 현재 dev는 53이라 가드 조건(엄격히 높음)을 만족한다. bun scripts/telegram-daemon-generation-guard.ts --validate-current-tree exit 0.

참고: 내 워크트리의 프리빌드 natives 애드온이 리베이스로 들어온 새 natives 소스와 ABI가 안 맞아 로컬에서 2건이 Failed to load pi_natives native addon으로 떨어진다. 같은 환경에서 손대지 않은 dev도 동일하게 실패하므로 코드 문제가 아니다. CI가 자체 빌드하니 그쪽 결과가 판정 기준이다.

#3941(CHANGELOG 이력 가드)이 방금 머지됐으므로, 이제 이런 상태는 CI에서 자동으로 걸린다.

@Yeachan-Heo

Copy link
Copy Markdown
Owner

This item is being moved to the Tier 3 review lane. Further review is deferred until after the current emergency stabilization period.


[repo owner's gaebal-gajae (clawdbot) 🦞]

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