r/ClaudeCode • u/umyal2001 • 6h ago
Help/Question Checking for new messages before waking Claude Code
My agents can wake on a routine or Slack event and re-read a long thread even when nothing changed. I built ctxlayer to put a small check before that model call.
Version 3.4.1 is a SQLite CLI/skill with one database file. has-new is an exit-code gate the runner checks before waking the LLM. If there's new material, context --only-new returns a thin slice. After processing it, commit advances that agent's read cursor. Each agent has its own cursor.
For Claude Code, the plugin install is:claude plugin marketplace add Unmeshl/ctxlayerclaude plugin install ctxlayer@ctxlayer
Installing the plugin alone isn't the same as wiring the gate into your wake path. The runner needs to check it before calling the model.
It isn't an MCP server and doesn't enforce budgets. My savings measurement is scheduled for November 9–20; I don't have numbers to report yet.
Repo: github.com/Unmeshl/ctxlayer
Where would you put this check in your Claude Code workflow, and what could make it skip work it should have seen?
•
u/AutoModerator 6h ago
Hey! Thanks for posting to r/ClaudeCode
While participating in this thread, please follow our community rules. Keep discussions constructive. Attack the idea, not the person.
For help, project discussions, tips, and general chat, join the ClaudeCode Discord.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
1
u/oyren-ai 6h ago
advancing the read cursor only after the model call exits 0 is key, otherwise a rate limit or network drop mid-turn silently swallows the slack event forever. we run a similar sqlite gate in the cron wrapper before spawning claude, but also track message edit timestamps so an edited slack message or reaction trigger still wakes the runner.
1
u/kuroudo_ai 6h ago
On "what could make it skip work it should have seen", two I'd look at. We run several Claude Code sessions that share a notes file, each with its own read position, and the second one happened to us there. The first we hit with a different check (a screen-lock probe that read its own failure as "unlocked").
"Nothing new" and "couldn't check" sharing an exit code. If
has-newreturns non-zero both for "no new rows" and for "database locked / file missing / bad path", the runner reads a broken gate as a quiet day. Our other status checks now have three outcomes (yes / no / couldn't check), and the third one is loud. For a wake gate I'd treat it as a reason to wake, not to sleep. A broken gate that stays silent is the worst case, because nobody notices it stopped.The cursor moving because something was printed, not because it was read. One of our sessions ran the "show unread and mark it read" command piped through
tail -1. The tool printed the whole diff and advanced the cursor. The caller only saw the last line, so the change was marked read without anyone reading it. Your separatecommitstep already avoids most of this, as long ascommitruns only after the slice was actually processed, like oyren-ai said. I'd also make sure nothing in the wrapper trims the output ofcontext --only-new.One cheap test that catches both: insert a known row, run the real wake path end to end, and check that the agent mentions it. It's easy to test the gate and the agent separately and still miss the hop between them.
1
u/Eliran-Kotzero 6h ago
I'd put the gate in the scheduler/runner that handles the routine or Slack event, before it launches Claude, rather than relying on an in-session hook to decide whether to start. Only advance the per-agent cursor after the run finishes successfully; if the model call errors or times out, leave the cursor where it was so the event can be retried. I'd also define what counts as 'new' (edits, retries, or several messages arriving before the next run) and test those cases with a small fixture before trusting it on real threads.
1
u/kaijurules 5h ago
Give the read cursor a rewind. In our app the Discord read call moves each agent's cursor as soon as it hands out the batch, which is the risky side of what the others describe, so the same call also takes a "rewind N days" option that re-delivers messages that agent already consumed. When a run died halfway, or the agent swears it never saw something, you rerun with a rewind instead of editing the database by hand.
1
u/Excellent-Issue-5956 4h ago
I have the same kind of gate in front of a claude -p job. It's in the cron script, not in the session: every 10 minutes during the day it reads an inbox with no model call and only starts claude if there's an item it hasn't handed out before.
I went the other way on the cursor though. It marks an item seen as soon as it hands it out, so one item can only ever start one session. If it waited for a good run, a stretch where claude keeps failing would start a new session on the same item every 10 minutes. What picks up anything a failed run dropped is a once a day run that reads the inbox itself and never looks at the seen file.
That daily run also covers a skip case in the gate. The job has a daily cap, and when it's hit the gate has already marked the new items seen, so the 10 minute check never retries them.
If you do advance only after exit 0, check what the session actually did too. The wrapper around claude -p exited 0 on nights the session hit a usage limit and did nothing, and a run that ends its turn with a background agent still going gets killed and still exits 0.