Skip to content

feat(update): /agent:update skill + heartbeat version-check#12

Merged
crisandrews merged 2 commits intocrisandrews:mainfrom
JD2005L:feat/update-skill
Apr 17, 2026
Merged

feat(update): /agent:update skill + heartbeat version-check#12
crisandrews merged 2 commits intocrisandrews:mainfrom
JD2005L:feat/update-skill

Conversation

@JD2005L
Copy link
Copy Markdown
Contributor

@JD2005L JD2005L commented Apr 15, 2026

Summary

Companion to #9 — which disables Claude Code's in-process auto-updater for service-mode installs. Without an explicit update path, daemonized agents silently fall behind. This PR closes that loop with a detect-and-notify flow that doesn't introduce the very class of mid-run mutation #9 set out to prevent.

Two pieces:

1. New /agent:update skill (skills/update/SKILL.md)

User-invocable. Triggers on /agent:update, /update, "check for updates", "are there updates", "buscar actualizaciones".

What it does:

  • Detects installed Claude Code (claude --version) vs latest on npm (npm view @anthropic-ai/claude-code version)
  • Detects local ClawCode HEAD vs upstream/main (when the plugin is a git checkout)
  • Prints a compact comparison
  • When updates are available, prints the safe procedure: explicit npm install -g, explicit git pull, explicit systemctl --user restart — the same atomic transition fix(service): wrap systemd ExecStart in PTY, disable auto-updater #9 wants users to take

What it deliberately doesn't do:

  • Apply updates. The claude user typically can't write to /usr/local/lib/node_modules/ (root-owned), and even where it can, auto-applying updates from inside the runtime is exactly what fix(service): wrap systemd ExecStart in PTY, disable auto-updater #9 disabled. The skill detects and reports; the operator runs the printed commands themselves.

2. Heartbeat template gains an "Update check" bullet (templates/HEARTBEAT.md)

One line added. Heartbeat fires every 30 min, but the actual network calls are gated:

  • Day-gate via memory/.last-update-check mtime — npm and GitHub don't need polling every 30 min, once per UTC day is plenty
  • Per-version dedupe via memory/.notified-versions.json — each new version is announced exactly once, not every cycle

When a new version is detected, the heartbeat surfaces a short one-line ping with the safe command. Mobile-friendly, no code-block walls.

Why this pairs with #9

PR #9 turns off the unsafe automatic path. This PR provides the safe manual path and makes sure users don't forget about it. They make sense together; either one without the other is incomplete:

I'd recommend landing them together. If they have to land separately, this one should land after #9 so the notification flow's first message isn't "you're 3 versions behind because we just turned off auto-update."

Permission constraints (worth flagging)

The skill is honest about what it can and can't do in different environments:

Setup What works
Service running as root Skill could in principle apply updates itself; this PR still doesn't (separation of concerns)
Service running as non-root, claude installed globally as root Detection works; install commands need to be run by the operator (typical case — we recommend this)
Claude installed under user-owned npm prefix Both detection and install would work; skill still detects-only

The "operator runs the command" pattern matches how a daemon should be operated — visible, explicit, audit-trail-able.

What's not changed

  • No new code in lib/ — pure skill + template addition
  • Existing agents' HEARTBEAT.md files are untouched (the template change applies to newly-created agents). Existing users who want the check can copy the bullet into their own HEARTBEAT.md.
  • Skill doesn't touch any cron registry or harness state — it's a pure read flow

Test plan

  • /agent:update from CLI: prints comparison block; prints update commands when versions differ; clean output when current
  • /agent:update via Telegram: short response, uses *bold* not **bold**, no walls of bash
  • No network: skill reports "unknown" for upstream values, doesn't error out
  • No git remote upstream: ClawCode side reports unknown, Claude Code side still works
  • Plugin not a git checkout: ClawCode side gracefully skipped, Claude Code side still works
  • Heartbeat day-gate: second invocation within 24h skips network calls (verify by checking memory/.last-update-check mtime)
  • Per-version dedupe: announcing version X writes to .notified-versions.json; subsequent heartbeats see X already-announced and stay quiet; new version Y triggers a new ping

🤖 Generated with Claude Code

Companion to crisandrews#9 (which disables Claude Code's in-process auto-updater for
service-mode installs). Without an explicit update path, daemonized agents
silently fall behind. This PR closes that loop:

1. New `/agent:update` skill (skills/update/SKILL.md, user-invocable):
   - Detects installed Claude Code (`claude --version`) vs latest on npm
     (`npm view @anthropic-ai/claude-code version`).
   - Detects local ClawCode HEAD vs upstream/main HEAD (works against any
     git remote name; caller's `upstream` if present, falls back gracefully
     when the plugin isn't a git checkout).
   - Reports a compact comparison and, when updates are available, prints
     the safe procedure: explicit `npm install -g`, explicit `git pull`,
     explicit `systemctl --user restart` — i.e. the same atomic transition
     PR crisandrews#9 wants users to take.
   - Detection-only. Does NOT execute the install. The `claude` user
     typically can't write to `/usr/local/lib/node_modules/`, and even
     where it can, auto-applying updates from inside the runtime is
     exactly what we just disabled.

2. Heartbeat template gains an "Update check" bullet
   (templates/HEARTBEAT.md) that:
   - Day-gates network calls via `memory/.last-update-check` (npm + GitHub
     don't need polling every 30 min).
   - Per-version dedupe via `memory/.notified-versions.json` so each new
     version is announced exactly once, not every cycle.
   - Pings the user with one short line containing the safe command —
     mobile-friendly, no code-block walls.

3. Channel-aware output: skill flow handles WhatsApp / Telegram / CLI
   formatting differences and uses the appropriate `reply` tool when on
   a messaging channel.

The skill is purely additive — agents that don't trigger it see no change.
The HEARTBEAT.md change applies only to newly-created agents (existing
agents have their own HEARTBEAT.md, which they can update by hand).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@crisandrews
Copy link
Copy Markdown
Owner

Hey JD, love it, detect-only is the right call.

One thing before merge: the ClawCode version check fires on any upstream commit (step 4 does rev-parse upstream/main). Could you switch to git describe --tags so it only pings on real releases? A docs commit on main showing up as "update available" would just teach people to ignore the pings.

Rest looks solid. Thanks!

JD2005L added a commit to JD2005L/ClawCode that referenced this pull request Apr 17, 2026
…every upstream commit

Per review on crisandrews#12 — the previous comparison tripped CW_UPDATE_AVAILABLE on any
commit ahead on upstream/main, including docs/chore/ci. In practice that means
a README tweak or a clone-count bump surfaces as "update available" and users
learn to ignore the notification.

Switch the comparison to reachable-release-tag: CW_UPDATE_AVAILABLE=1 only when
CW_LOCAL_TAG and CW_UPSTREAM_TAG are both set, neither is "no-tag", and they
differ. The raw commit hashes are still gathered and still shown in the output
block for diagnostic purposes — they just don't drive the notify signal.

Silent-check dedupe is keyed by CW_UPSTREAM_TAG, so each release is announced
exactly once; pure upstream chore activity produces no pings at all.
@JD2005L
Copy link
Copy Markdown
Contributor Author

JD2005L commented Apr 17, 2026

Good catch. Switched CW_UPDATE_AVAILABLE to tag-based. It now only fires when git describe --tags --abbrev=0 upstream/main differs from the local tag. Raw HEAD still renders in the diagnostic block for debugging but doesn't drive the notify signal anymore. Silent-check dedupe keys on the tag too, so each release is announced exactly once and chore/docs/ci traffic stays silent.

Pushed as 17e0423. Much better this way. Thanks!

…every upstream commit

Per review on crisandrews#12, the previous comparison tripped CW_UPDATE_AVAILABLE on any
commit ahead on upstream/main, including docs/chore/ci. In practice that means
a README tweak or a clone-count bump surfaces as "update available" and users
learn to ignore the notification.

Switch the comparison to reachable-release-tag: CW_UPDATE_AVAILABLE=1 only when
CW_LOCAL_TAG and CW_UPSTREAM_TAG are both set, neither is "no-tag", and they
differ. The raw commit hashes are still gathered and still shown in the output
block for diagnostic purposes. They just don't drive the notify signal.

Silent-check dedupe is keyed by CW_UPSTREAM_TAG, so each release is announced
exactly once; pure upstream chore activity produces no pings at all.
@JD2005L JD2005L force-pushed the feat/update-skill branch from 1fe0906 to 17e0423 Compare April 17, 2026 02:05
@crisandrews crisandrews merged commit ddefcf4 into crisandrews:main Apr 17, 2026
JD2005L added a commit to JD2005L/ClawCode that referenced this pull request Apr 17, 2026
Follow-up to crisandrews#16 with a clarified rationale for keeping the env var.

The PTY wrap from crisandrews#9 fixes the SessionEnd-hook failure that turns graceful
exit into code 1 and causes restart churn. DISABLE_AUTOUPDATER=1 addresses
a different problem: Claude Code's in-process auto-updater regenerates
files it manages mid-run, including the resume-on-restart wrapper script
generated by crisandrews#7. A long-running daemon rewriting its own ExecStart target
while live is a file-integrity issue, separate from the crash loop, and
the PTY wrap does nothing for it.

On the "don't modify Claude Code internals" principle from crisandrews#16: the
principle stands, but DISABLE_AUTOUPDATER is a documented env var Claude
Code exposes for this use case. Setting it is a supported interface, not
a monkey-patch. The restored comment names the env var and the specific
file-regeneration scenario inline so future readers see the intent.

Also relevant to crisandrews#12 (/agent:update skill): the skill's explicit-manual-
update flow assumes in-process auto-update is off in service mode. With
auto-update running again, the skill competes with an updater that may
silently rewrite service files behind the operator.
crisandrews added a commit that referenced this pull request Apr 17, 2026
Summary of changes in this release (full detail in CHANGELOG.md):

Added
- Resume-on-restart wrapper for service mode (#7)
- Service hardening defaults: HOME/TERM env, StartLimitBurst guard, persistent log path (#8)
- /agent:update skill + heartbeat version-check with day-gate and per-version dedupe (#12)

Fixed
- WORKSPACE resolution so memory_search hits user's project dir, not plugin dir (#6, closes #5)
- Linux systemd crash loop after Claude Code auto-updates mid-run — PTY wrap in ExecStart + DISABLE_AUTOUPDATER=1 for file-integrity (#9, #17/#18)
- macOS launchd PTY wrap parity (#16)
- Cross-user /agent:import discovery + post-import path sanity check (#10)

Performance
- reconcile-crons.sh fast-path on steady-state sessions (#11)

Thanks to @JD2005L for the whole batch.
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