Stop phantom-notification hangs and drop noisy "0초" progress label
Agents ended turns with "I'll wait … I'll be notified" expecting a background-completion callback that this environment never delivers, so the turn hung and left the planning sentence stuck in the channel. Add a claude-platform rule that no such notification exists — finish within the turn (foreground/poll) or ask the user for a follow-up. Also suppress the elapsed-time suffix on progress messages until at least one 5s bucket has elapsed, so freshly-created progress no longer shows a meaningless "0초". Update the affected message-runtime expectations. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,18 @@ Use it to acknowledge a request before starting longer work.
|
|||||||
|
|
||||||
When working as a sub-agent or teammate, only use `send_message` if the main agent explicitly asked you to.
|
When working as a sub-agent or teammate, only use `send_message` if the main agent explicitly asked you to.
|
||||||
|
|
||||||
|
## Background work never notifies you — finish within the turn
|
||||||
|
|
||||||
|
Nothing in this environment re-invokes you when a background process, a background shell command, or a `run_in_background` job finishes. You will NOT be automatically notified when a build, compile, download, deploy, or any background task completes. The built-in tool descriptions that say "you'll be notified when it finishes" do NOT apply here.
|
||||||
|
|
||||||
|
If you end your turn with a sentence like "I'll wait for the build to complete — I'll be notified", the turn simply ends and nothing ever resumes — the user is left stuck staring at that message. This is a real, repeated failure. Do not do it.
|
||||||
|
|
||||||
|
Therefore:
|
||||||
|
|
||||||
|
- Do not launch long work in the background and then end the turn expecting a callback. Run it in the foreground and block/poll until it actually finishes inside this same turn, then report the real result.
|
||||||
|
- If something genuinely cannot complete within one turn, do not pretend a notification is coming. Finish with a concrete status and explicitly ask the user to send a follow-up message to continue. For recurring polling of CI/scheduled work, use the `schedule_task` / `watch_ci` tools, which DO re-invoke you.
|
||||||
|
- Never leave an intermediate "I'll wait … I'll be notified" planning sentence as your final answer.
|
||||||
|
|
||||||
## Media attachments
|
## Media attachments
|
||||||
|
|
||||||
When a locally generated image, screenshot, video, audio, or document should appear in Discord, include a `MEDIA:` directive on its own line with an absolute local path:
|
When a locally generated image, screenshot, video, audio, or document should appear in Discord, include a `MEDIA:` directive on its own line with an absolute local path:
|
||||||
|
|||||||
@@ -4172,7 +4172,7 @@ describe('createMessageRuntime', () => {
|
|||||||
// First progress flushed when the second progress arrives
|
// First progress flushed when the second progress arrives
|
||||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||||
chatJid,
|
chatJid,
|
||||||
P('CI 상태 확인 중입니다.\n\n0초'),
|
P('CI 상태 확인 중입니다.'),
|
||||||
);
|
);
|
||||||
// Timer tick edits the tracked progress with updated elapsed time
|
// Timer tick edits the tracked progress with updated elapsed time
|
||||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||||
@@ -4272,11 +4272,11 @@ describe('createMessageRuntime', () => {
|
|||||||
// First progress flushed when second arrives — sendAndTrack throws, falls back to sendMessage
|
// First progress flushed when second arrives — sendAndTrack throws, falls back to sendMessage
|
||||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||||
chatJid,
|
chatJid,
|
||||||
P('진행 중입니다.\n\n0초'),
|
P('진행 중입니다.'),
|
||||||
);
|
);
|
||||||
expect(channel.sendMessage).toHaveBeenCalledWith(
|
expect(channel.sendMessage).toHaveBeenCalledWith(
|
||||||
chatJid,
|
chatJid,
|
||||||
P('진행 중입니다.\n\n0초'),
|
P('진행 중입니다.'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4353,11 +4353,11 @@ describe('createMessageRuntime', () => {
|
|||||||
// First progress flushed when second arrives — sendAndTrack returns null, falls back to sendMessage
|
// First progress flushed when second arrives — sendAndTrack returns null, falls back to sendMessage
|
||||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||||
chatJid,
|
chatJid,
|
||||||
P('진행 중입니다.\n\n0초'),
|
P('진행 중입니다.'),
|
||||||
);
|
);
|
||||||
expect(channel.sendMessage).toHaveBeenCalledWith(
|
expect(channel.sendMessage).toHaveBeenCalledWith(
|
||||||
chatJid,
|
chatJid,
|
||||||
P('진행 중입니다.\n\n0초'),
|
P('진행 중입니다.'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4461,7 +4461,7 @@ describe('createMessageRuntime', () => {
|
|||||||
expect(channel.sendAndTrack).toHaveBeenCalledTimes(1);
|
expect(channel.sendAndTrack).toHaveBeenCalledTimes(1);
|
||||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||||
chatJid,
|
chatJid,
|
||||||
P('첫 번째 진행상황입니다.\n\n0초'),
|
P('첫 번째 진행상황입니다.'),
|
||||||
);
|
);
|
||||||
// Timer tick updates tracked progress via edit
|
// Timer tick updates tracked progress via edit
|
||||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||||
@@ -4573,7 +4573,7 @@ describe('createMessageRuntime', () => {
|
|||||||
// First progress flushed when second arrives
|
// First progress flushed when second arrives
|
||||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||||
chatJid,
|
chatJid,
|
||||||
P('오래 걸리는 작업입니다.\n\n0초'),
|
P('오래 걸리는 작업입니다.'),
|
||||||
);
|
);
|
||||||
// Timer ticks update the tracked progress with longer durations
|
// Timer ticks update the tracked progress with longer durations
|
||||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||||
@@ -4679,7 +4679,7 @@ describe('createMessageRuntime', () => {
|
|||||||
// First progress flushed when second arrives
|
// First progress flushed when second arrives
|
||||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||||
chatJid,
|
chatJid,
|
||||||
P('테스트를 돌리는 중입니다.\n\n0초'),
|
P('테스트를 돌리는 중입니다.'),
|
||||||
);
|
);
|
||||||
// Timer tick updates tracked progress
|
// Timer tick updates tracked progress
|
||||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||||
@@ -5088,13 +5088,13 @@ describe('createMessageRuntime', () => {
|
|||||||
expect(channel.sendAndTrack).toHaveBeenNthCalledWith(
|
expect(channel.sendAndTrack).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
chatJid,
|
chatJid,
|
||||||
P('첫 번째 진행상황입니다.\n\n0초'),
|
P('첫 번째 진행상황입니다.'),
|
||||||
);
|
);
|
||||||
// After empty final resets state, third progress flushed when fourth arrives (new message)
|
// After empty final resets state, third progress flushed when fourth arrives (new message)
|
||||||
expect(channel.sendAndTrack).toHaveBeenNthCalledWith(
|
expect(channel.sendAndTrack).toHaveBeenNthCalledWith(
|
||||||
2,
|
2,
|
||||||
chatJid,
|
chatJid,
|
||||||
P('두 번째 진행상황입니다.\n\n0초'),
|
P('두 번째 진행상황입니다.'),
|
||||||
);
|
);
|
||||||
// Timer tick edits the first tracked progress
|
// Timer tick edits the first tracked progress
|
||||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||||
@@ -5213,7 +5213,7 @@ describe('createMessageRuntime', () => {
|
|||||||
// First progress flushed when second arrives
|
// First progress flushed when second arrives
|
||||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||||
chatJid,
|
chatJid,
|
||||||
P('검증 중입니다.\n\n0초'),
|
P('검증 중입니다.'),
|
||||||
);
|
);
|
||||||
// Once the tracked progress exists, later progress updates replace the visible heading directly.
|
// Once the tracked progress exists, later progress updates replace the visible heading directly.
|
||||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||||
@@ -5329,7 +5329,7 @@ describe('createMessageRuntime', () => {
|
|||||||
// The first flushed progress is still tracked
|
// The first flushed progress is still tracked
|
||||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||||
chatJid,
|
chatJid,
|
||||||
P('진행 중입니다.\n\n0초'),
|
P('진행 중입니다.'),
|
||||||
);
|
);
|
||||||
// Edit is attempted on the tracked message (first fails, subsequent succeed)
|
// Edit is attempted on the tracked message (first fails, subsequent succeed)
|
||||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||||
@@ -5568,7 +5568,7 @@ describe('createMessageRuntime', () => {
|
|||||||
// First progress flushed when second arrives
|
// First progress flushed when second arrives
|
||||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||||
chatJid,
|
chatJid,
|
||||||
P('중간 진행상황입니다.\n\n0초'),
|
P('중간 진행상황입니다.'),
|
||||||
);
|
);
|
||||||
// Error replaces the tracked progress message with the failure final
|
// Error replaces the tracked progress message with the failure final
|
||||||
expect(channel.sendMessage).not.toHaveBeenCalled();
|
expect(channel.sendMessage).not.toHaveBeenCalled();
|
||||||
|
|||||||
@@ -496,7 +496,10 @@ export class MessageTurnController {
|
|||||||
? 0
|
? 0
|
||||||
: Math.floor((Date.now() - this.progressStartedAt) / 5_000) * 5000;
|
: Math.floor((Date.now() - this.progressStartedAt) / 5_000) * 5000;
|
||||||
|
|
||||||
const suffix = `\n\n${formatElapsedKorean(elapsedMs)}`;
|
// Skip the elapsed-time label until at least one 5s bucket has elapsed, so
|
||||||
|
// a freshly-created progress message doesn't show a meaningless "0초".
|
||||||
|
const suffix =
|
||||||
|
elapsedMs >= 5000 ? `\n\n${formatElapsedKorean(elapsedMs)}` : '';
|
||||||
const body = this.composeProgressBody(text);
|
const body = this.composeProgressBody(text);
|
||||||
|
|
||||||
this.persistProgressBody(body);
|
this.persistProgressBody(body);
|
||||||
|
|||||||
Reference in New Issue
Block a user