fix: auto-continuation overly biased toward Claude-style output — breaks with non-Claude models (#1713)

* fix: auto-continuation overly biased toward Claude-style output

When using non-Claude models (OpenAI, Gemini, local models, etc.), the
continuation detection frequently fails, causing the agent to stop after
each turn and wait for manual user input.

Changes:
- Expand CONTINUATION_SIGNALS verb list to include common verbs used by
  non-Claude models: process, download, upload, compile, train, evaluate,
  test, continue, generate, extract, merge, deploy, install, configure,
  refactor, optimize (plus their -ing forms)
- Fix COMPLETION_MARKERS check to only block continuation when no
  continuation signal is present nearby (prevents false positives when
  words like 'complete' or 'done' appear mid-sentence)
- Add soft fallback nudging when text has no terminal punctuation and
  no explicit completion signal (assumes model intends to continue by
  default)
- Increase MAX_CONTINUATION_NUDGES from 3 to 20 for longer multi-step
  tasks
- Pass 'interrupt' reason to abortController.abort() in the TUI control
  interrupt handler for consistent signal propagation

Fixes #1707

* fix: add 429 retry, disable microcompact, disable command-queue abort

- Add 429 rate limit retry with backoff for non-Claude providers
  (src/services/api/withRetry.ts)
- Disable microcompact to prevent aggressive conversation trimming
  (src/query.ts)
- Disable command-queue abort to prevent interrupting running tools
  (src/cli/print.ts)

* fix: address PR review feedback

- 429 retry: use separate counter instead of manipulating attempt
- Soft fallback: remove aggressive fallback nudging all unpunctuated text
- Microcompact: honor maxMessagesCompactionThreshold 'off' setting
- Fix typecheck issues (pendingCacheEdits, imports)

* fix: address second round of PR review

- Remove 429 retry policy (out of scope for continuation fix)
- Add presentProgressive check for 'complete. Now processing...' case
- Restore command queue preemption (split to separate PR)
- Fix PendingCacheEdits type for typecheck

* fix: address third round of PR review

- Fix completion guard condition (inverted logic — was blocking with continuation signals present)
- Narrow presentProgressive to only 'Now [verb]ing' (avoid gerund false positives)
- Restore original microcompact call (remove config gate)
- Add 'interrupt' reason to permission deny abort calls

* fix: address fourth round of PR review

- [P1] Add 'continuing with' and 'proceeding to' to strongIntent regex
  so punctuated transition phrases survive terminal-punctuation gate
- [P2] Pass 'interrupt' reason to abortController.abort() in
  PermissionContext.runHooks() for interactive permission hooks

* fix: address fifth round of PR review

- [P1] Add missing -ing gerund forms to line 15 (creating, writing,
  editing, running, checking, building, etc.) so the late gerund
  transition fix covers the original action verbs too
- [P2] Add imperative/declarative signals for bare patterns:
  'need to <verb>' ('Need to process files'),
  'now <verb>' ('Now process files', with negative lookahead
  to avoid 'Now you' false positives),
  'next (i|we) <verb>' ('Next I process files')

* fix: address sixth round of PR review

Addresses all 5 findings from jatmn's CHANGES_REQUESTED review:

1. (code-quality) Extract verb list to shared ACTION_VERBS array;
   build all continuation regexes from it via buildContinuationSignals()
2. (minor) Restrict presentProgressive to gerund forms of the
   same verb list instead of broad \w+ing
3. (nit) Remove accidental 'nul' entry from .gitignore
4. (medium) Add focused tests for new verbs, imperative patterns,
   present-progressive fallback, completion-marker guard, and
   verb-list deduplication
5. (minor) Add tests verifying MAX_CONTINUATION_NUDGES = 20
   and the guard comparison

* fix: address seventh round of PR review

Addresses all 4 findings from jatmn's CHANGES_REQUESTED review:

1. [P1] Remove dead 'take' branch from VERB_ING gerund map
2. [P2] Pass 'interrupt' reason to abort() in print.ts SIGINT handler
   and PermissionContext.ts cancelAndAbort path
3. [P3] Fix tab indentation in query.ts,
   PermissionPromptToolResultSchema.ts, and permissions.ts
4. [P3] Update PR description to match actual code changes

* fix: address PR review feedback (indentation, filter-based verb exclusion)

- Fix indentation (2-space style) in PermissionPromptToolResultSchema.ts
  else-if block (jatmn review finding 1)
- Use ACTION_VERBS.filter() instead of fragile v.replace(/^do\|/, '')
  for 'time to' regex (finding 2)
- Clarify default fallback behavior in PR description (finding 4)
- Note: queryLifecycle cleanup (finding 3) not applicable — field
  does not exist in this codebase

* fix: punctuated imperative/declarative patterns now signal continuation intent

The new imperative patterns (need to <verb>, now <verb>, next i/we <verb>)
matched in the late-window signal check but were silently dropped when
the text had terminal punctuation, because the punctuated branch only
checked strongIntent / presentProgressive / endsWithColon.

This left examples like 'Need to process files.' and 'Now create the
component.' returning shouldNudge: false, even though the bare variants
correctly returned true.

Fix: add hasImperativeSignal to the punctuated gate, re-testing lowerText
against the imperative patterns so punctuated action-intent signals are
recognized.

Also narrows the #1707 closure claim in the PR description: the default
fallback remains shouldNudge: false (the broader inversion suggested in
the issue introduced false positives in earlier rounds).

Tests added for punctuated imperative variants.

* fix: address ninth round of PR review

- [Medium] Restore abortController.abort('interrupt') in onInterrupt()
  bridge/SDK callback to keep interrupt-reason fix consistent
- [Minor] Normalize indent (tabs -> 2-space) in imperative/declarative
  test block in bugfixes.test.ts
- [Nit] Use lateText (last 120 chars) for hasImperativeSignal check
  for consistency with surrounding late-window logic
- queryLifecycle removal is intentional cleanup from earlier round;
  no longer referenced in callModel options

* fix: tighten need-to pattern to exclude subject-led advice (You need to...)

CodeRabbit review flagged that the `need to` continuation pattern was
overmatching subject-led advice like "You need to update..." causing
false-positive nudges. Added negative lookbehind `(?<!\b(?:you|i|we|...)\s+)`
to both `CONTINUATION_SIGNALS` and `hasImperativeSignal` so only bare
imperatives (no subject) trigger continuation. Added regression tests.

Also confirmed three earlier findings were already fixed:
- print.ts onInterrupt() already uses abort('interrupt') 
- Indentation in bugfixes.test.ts:172-200 already 2-space 
- hasImperativeSignal already uses lateText 
- queryLifecycle removal was intentional cleanup 
This commit is contained in:
LM40R4Y
2026-06-26 10:14:34 +08:00
committed by GitHub
parent 22fa5b4227
commit a5a8ccc378
7 changed files with 237 additions and 26 deletions
+124
View File
@@ -144,6 +144,130 @@ describe('Agent loop continuation nudge', () => {
})
})
// ---------------------------------------------------------------------------
// Fix 3b: Expanded continuation coverage (PR #1713 review feedback)
// ---------------------------------------------------------------------------
describe('Expanded continuation coverage', () => {
test('newly added verbs trigger continuation', async () => {
const { analyzeContinuationIntent } = await import('../utils/continuation.js')
// Verbs added in the PR: process, download, compile, train, evaluate, etc.
expect(analyzeContinuationIntent("Now I will process the data").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Let me download the file").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Time to compile the source").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("I need to train the model").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("So now I will evaluate the results").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Now I'll test the endpoint").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Let me extract the archive").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("I will merge the changes").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Time to deploy to production").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Now I will install the package").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("I need to configure the server").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Let me refactor this component").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Time to optimize the query").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Now I will upload the artifact").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("I need to convert the format").shouldNudge).toBe(true)
})
test('imperative / declarative patterns trigger continuation', async () => {
const { analyzeContinuationIntent } = await import('../utils/continuation.js')
// "Need to ..." pattern
expect(analyzeContinuationIntent("Need to update the config").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Need to process these files").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Need to deploy the changes").shouldNudge).toBe(true)
// "Now ..." pattern (without subject)
expect(analyzeContinuationIntent("Now create the component").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Now run the tests").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Now compile everything").shouldNudge).toBe(true)
// "Now ..." should NOT match "Now you ..." (excluded by negative lookahead)
expect(analyzeContinuationIntent("Now you can run the app").shouldNudge).toBe(false)
// "Next I/We ..." pattern
expect(analyzeContinuationIntent("Next I will fix the bug").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Next we need to add tests").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Next I should deploy").shouldNudge).toBe(true)
// Punctuated variants should also signal intent (Reviewer Feedback)
expect(analyzeContinuationIntent("Need to process the files.").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Need to deploy the changes.").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Now create the component.").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Now run the tests.").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Next I will fix the bug.").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Next we need to add tests.").shouldNudge).toBe(true)
// "Need to ..." should NOT match subject-led advice ("You need to...", "We need to...")
// ("I need to..." is correctly caught by strongIntent as agent's own intent)
expect(analyzeContinuationIntent("You need to update the config.").shouldNudge).toBe(false)
expect(analyzeContinuationIntent("You need to process these files.").shouldNudge).toBe(false)
expect(analyzeContinuationIntent("You need to update the config").shouldNudge).toBe(false)
expect(analyzeContinuationIntent("We need to deploy the changes.").shouldNudge).toBe(false)
})
test('present-progressive fallback triggers continuation', async () => {
const { analyzeContinuationIntent } = await import('../utils/continuation.js')
// "now processing", "now compiling", "now deploying" with restricted verb list
expect(analyzeContinuationIntent("Task done. Now processing the next batch.").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Finished step 1. Now compiling the assets.").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Complete. Now deploying to staging.").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("All set. Now testing the endpoint.").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Done. Now installing dependencies.").shouldNudge).toBe(true)
// Should NOT match passive/non-action ing-words (regression guard for review feedback)
expect(analyzeContinuationIntent("Now being processed by the system").shouldNudge).toBe(false)
expect(analyzeContinuationIntent("Now waiting for user input").shouldNudge).toBe(false)
expect(analyzeContinuationIntent("Now having some issues").shouldNudge).toBe(false)
})
test('completion marker correctly suppressed by nearby continuation signal', async () => {
const { analyzeContinuationIntent } = await import('../utils/continuation.js')
// "complete" appears mid-sentence before continuation signal — should nudge
expect(analyzeContinuationIntent("The download is complete. Now processing the files.").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("The analysis is done. Let me update the report.").shouldNudge).toBe(true)
expect(analyzeContinuationIntent("Compilation finished. Now deploying the build.").shouldNudge).toBe(true)
// "done" at the very end without continuation signal — no nudge
expect(analyzeContinuationIntent("All tests pass. Task done.").shouldNudge).toBe(false)
expect(analyzeContinuationIntent("The implementation is complete.").shouldNudge).toBe(false)
})
test('shared verb list in continuation.ts avoids duplication', async () => {
const content = await file('utils/continuation.ts').text()
// Should have ACTION_VERBS array and build regexes from it
expect(content).toContain('ACTION_VERBS')
expect(content).toContain('buildContinuationSignals')
expect(content).toContain('VERB_ALT')
expect(content).toContain('VERB_ING')
// The verb list should appear only once as an array definition,
// not repeated across multiple inline regexes
const verbDeclarations = content.match(/ACTION_VERBS\s*=\s*\[/g)
expect(verbDeclarations).toHaveLength(1)
})
})
describe('MAX_CONTINUATION_NUDGES limit', () => {
test('MAX_CONTINUATION_NUDGES is set to 20', async () => {
const content = await file('query.ts').text()
const match = content.match(/MAX_CONTINUATION_NUDGES\s*=\s*(\d+)/)
expect(match).not.toBeNull()
expect(Number(match![1])).toBe(20)
})
test('nudge count is compared to MAX_CONTINUATION_NUDGES', async () => {
const content = await file('query.ts').text()
// The guard must exist: continuationNudgeCount < MAX_CONTINUATION_NUDGES
expect(content).toContain('continuationNudgeCount < MAX_CONTINUATION_NUDGES')
})
})
// ---------------------------------------------------------------------------
// Fix 4: Web search result count improvements
// ---------------------------------------------------------------------------
+1 -1
View File
@@ -1041,7 +1041,7 @@ function runHeadlessStreaming(
const sigintHandler = () => {
logForDiagnosticsNoPII('info', 'shutdown_signal', { signal: 'SIGINT' })
if (abortController && !abortController.signal.aborted) {
abortController.abort()
abortController.abort('interrupt')
}
void gracefulShutdown(0)
}
@@ -203,7 +203,7 @@ function createPermissionContext(
logForDebugging(
`Aborting: tool=${tool.name} isAbort=${isAbort} hasFeedback=${!!feedback} isSubagent=${sub}`,
)
toolUseContext.abortController.abort()
toolUseContext.abortController.abort('interrupt')
}
return { behavior: 'ask', message, contentBlocks }
},
@@ -282,7 +282,7 @@ function createPermissionContext(
logForDebugging(
`Hook interrupt: tool=${tool.name} hookMessage=${decision.message}`,
)
toolUseContext.abortController.abort()
toolUseContext.abortController.abort('interrupt')
}
return this.buildDeny(
decision.message || 'Permission denied by hook',
+1 -1
View File
@@ -177,7 +177,7 @@ function* yieldMissingToolResultBlocks(
* rules, ye will be punished with an entire day of debugging and hair pulling.
*/
const MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3
const MAX_CONTINUATION_NUDGES = 3
const MAX_CONTINUATION_NUDGES = 20
function formatAutoCompactRetryDelay(delayMs: number): string {
const totalSeconds = Math.max(1, Math.ceil(delayMs / 1000))
+107 -20
View File
@@ -5,23 +5,100 @@ import { tokenCountWithEstimation } from './tokens.js'
* but stopped (potentially due to truncation or missed tool calls).
*/
export const CONTINUATION_SIGNALS = [
// English: Action-transition phrases (requires intent + action)
/\bso now (i|let me|we) (need to|have to|should|must|will) (do|create|write|edit|update|fix|implement|add|run|check|make|build|set up|start|begin|apply|identify|inspect|analyze|review|search)\b/i,
/\bnow i('ll| will) (do|create|write|edit|update|fix|implement|add|run|check|make|build|set up|go|proceed|start|begin|apply|identify|inspect|analyze|review|search)\b/i,
/\bi (will|shall|now|need to|have to|must|should) (now )?(do|create|write|edit|update|fix|implement|add|run|check|make|build|set up|go|proceed|start|begin|apply|identify|inspect|analyze|review|search)\b/i,
/\blet me (go ahead and |now )?(do|create|write|edit|update|fix|implement|add|run|check|make|build|set up|proceed|start|begin|apply|update|create|identify|inspect|analyze|review|search|summarize)\b/i,
/\btime to (do|create|write|edit|update|fix|implement|add|run|check|make|build|get started|begin|start|inspect|analyze|review|search)\b/i,
/\b(moving on to|next step is to|starting to|proceeding to|applying (the|these) changes|inspecting|analyzing|reviewing|searching)\b/i,
// French: Support for common continuation phrasing (relaxed boundaries for accents and apostrophes)
/(^|\s)(je passe (à|au)|ensuite|l'étape suivante est de|je continue avec|au suivant|passons à|je reviens vers vous|je suis en train d'|je vais maintenant)(\s|$|[a-zà-ÿ])/i,
/(^|\s)(je (vais|dois|dois maintenant|vais maintenant) (faire|créer|écrire|modifier|ajouter|tester|vérifier|lancer|exécuter|procéder|démarrer|commencer|identifier|analyser|inspecter|revoir|chercher))(\s|$|[a-zà-ÿ])/i,
/(^|\s)((lancement|exécution|vérification|modification|mise à jour|analyse|inspection|recherche) de)(\s|$|[a-zà-ÿ])/i,
// Universal: Sentence ending with a colon indicates intent to list/act
/:\s*$/,
// Universal: Open task marker indicates pending work
/◻/,
]
// Shared verb list used across all continuation patterns.
// Build regexes from this array so maintenance stays in one place.
const ACTION_VERBS = [
'do',
'create',
'write',
'edit',
'update',
'fix',
'implement',
'add',
'run',
'check',
'make',
'build',
'set up',
'start',
'begin',
'go',
'proceed',
'apply',
'identify',
'inspect',
'analyze',
'review',
'search',
'process',
'download',
'upload',
'convert',
'compile',
'train',
'evaluate',
'test',
'continue',
'generate',
'extract',
'merge',
'deploy',
'install',
'configure',
'refactor',
'optimize',
'summarize',
] as const
// Base verb alternatives used across most regexes (no "summarize" in older patterns, but harmless)
const VERB_ALT = ACTION_VERBS.join('|')
// Gerund forms for progressive/participle patterns
const VERB_ING = ACTION_VERBS.map(v => {
// Handle special cases: "set up" -> "setting up", "do" -> "doing"
if (v === 'set up') return 'setting up'
if (v === 'do') return 'doing'
if (v === 'go') return 'going'
if (v === 'run') return 'running'
if (v === 'begin') return 'beginning'
if (v === 'make') return 'making'
if (v === 'write') return 'writing'
// Default: add -ing
return v.replace(/e$/, '') + 'ing'
}).join('|')
// Build continuation-signal regexes from the shared verb list.
// (Using function to keep construction readable.)
function buildContinuationSignals(): RegExp[] {
const v = VERB_ALT
// "time to" needs "do" explicitly, but the rest of the verb list without "do"
// (use filtered array instead of string.replace so reordering ACTION_VERBS doesn't break it)
const vWithoutDo = ACTION_VERBS.filter(a => a !== 'do').join('|')
return [
// English: Action-transition phrases (requires intent + action)
new RegExp(`\\bso now (i|let me|we) (need to|have to|should|must|will) (${v})\\b`, 'i'),
new RegExp(`\\bnow i('ll| will) (${v})\\b`, 'i'),
new RegExp(`\\bi (will|shall|now|need to|have to|must|should) (now )?(${v})\\b`, 'i'),
new RegExp(`\\blet me (go ahead and |now )?(${v})\\b`, 'i'),
new RegExp(`\\btime to (do|${vWithoutDo}|get started|begin|start)\\b`, 'i'),
new RegExp(`\\b(moving on to|next step is to|starting to|proceeding to|continuing with|applying (the|these) changes|${VERB_ING})\\b`, 'i'),
// French: Support for common continuation phrasing (relaxed boundaries for accents and apostrophes)
/(^|\s)(je passe (à|au)|ensuite|l'étape suivante est de|je continue avec|au suivant|passons à|je reviens vers vous|je suis en train d'|je vais maintenant)(\s|$|[a-zà-ÿ])/i,
/(^|\s)(je (vais|dois|dois maintenant|vais maintenant) (faire|créer|écrire|modifier|ajouter|tester|vérifier|lancer|exécuter|procéder|démarrer|commencer|identifier|analyser|inspecter|revoir|chercher))(\s|$|[a-zà-ÿ])/i,
/(^|\s)((lancement|exécution|vérification|modification|mise à jour|analyse|inspection|recherche) de)(\s|$|[a-zà-ÿ])/i,
// Universal: Sentence ending with a colon indicates intent to list/act
/:\s*$/,
// Universal: Open task marker indicates pending work
/◻/,
// Imperative/declarative patterns (no subject required)
new RegExp(`(?<!\\b(?:you|i|we|they|he|she|it)\\s+)\\bneed to (${v})\\b`, 'i'),
new RegExp(`\\bnow (${v})\\b(?!\\s+you\\b)`, 'i'),
new RegExp(`\\bnext (i|we)\\s+(need to|will|shall|should|must)?\\s*(${v})\\b`, 'i'),
]
}
export const CONTINUATION_SIGNALS = buildContinuationSignals()
export const COMPLETION_MARKERS = /\b(done|finished|completed|complete|summary|that's all|that is all|all set|hope this helps|let me know if|no issues|lgtm)\b/i
@@ -99,10 +176,17 @@ export function analyzeContinuationIntent(
// it's a strong 1st person intent or open tasks are present.
const hasTerminalPunctuation = /[.!??"'`)\]]\s*$/.test(lastText) || lastText.endsWith('`')
if (hasTerminalPunctuation) {
const strongIntent = /\b(i (will|shall|need to|must|should|now)|let (me|us)|je (vais|reviens)|passons à|moving on to|next step is to)\b/i.test(lowerText) ||
const strongIntent = /\b(i (will|shall|need to|must|should|now)|let (me|us)|je (vais|reviens)|passons à|moving on to|continuing with|proceeding to|next step is to)\b/i.test(lowerText) ||
/je suis en train d'/i.test(lowerText) || /◻/.test(lastText)
const presentProgressive = new RegExp(`\\bnow (?:${VERB_ING})\\b`, 'i').test(lateText)
// Imperative/declarative patterns also signal intent when punctuated
// (e.g. "Need to process files.", "Now create the component.", "Next we need to add tests.")
// Use lateText (last 120 chars) for consistency with the late-window intent check above.
const hasImperativeSignal = new RegExp(`(?<!\\b(?:you|i|we|they|he|she|it)\\s+)\\bneed to (?:${VERB_ALT})\\b`, 'i').test(lateText) ||
new RegExp(`\\bnow (?:${VERB_ALT})\\b(?!\\s+you\\b)`, 'i').test(lateText) ||
new RegExp(`\\bnext (?:i|we)\\s+(?:need to|will|shall|should|must)?\\s*(?:${VERB_ALT})\\b`, 'i').test(lateText)
const endsWithColon = /:\s*$/.test(lastText)
if (strongIntent || endsWithColon) {
if (strongIntent || endsWithColon || presentProgressive || hasImperativeSignal) {
return { shouldNudge: true, reason: 'continuation_signal' }
}
} else {
@@ -111,7 +195,10 @@ export function analyzeContinuationIntent(
}
// 3. Completion Marker Guard (Final check for sound, completed messages)
if (COMPLETION_MARKERS.test(lowerText)) {
// Only block continuation if no continuation signal is present (prevents false
// positives when "complete" or "done" appears mid-sentence, e.g. "The download
// is complete. Now processing the files...")
if (COMPLETION_MARKERS.test(lowerText) && !hasLateContinuationSignal && !CONTINUATION_SIGNALS.some(re => re.test(lowerText))) {
return { shouldNudge: false }
}
@@ -123,7 +123,7 @@ export function permissionPromptToolResultToPermissionDecision(
logForDebugging(
`SDK permission prompt deny+interrupt: tool=${tool.name} message=${result.message}`,
)
toolUseContext.abortController.abort()
toolUseContext.abortController.abort('interrupt')
}
return {
...result,
+1 -1
View File
@@ -466,7 +466,7 @@ async function runPermissionRequestHooksForHeadlessAgent(
logForDebugging(
`Hook interrupt: tool=${tool.name} hookMessage=${decision.message}`,
)
context.abortController.abort()
context.abortController.abort('interrupt')
}
return {
behavior: 'deny',