mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-01 15:47:41 +08:00
fix: Tell each codespace agent-worker turn that a turn is atomic (no-changelog) (#36304)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -71,6 +71,19 @@ A turn stops after about 25 minutes (`TURN_TIMEOUT_MS`). This limit is below the
|
||||
n8n Wait limit. So the worker reports a clear message before n8n reports a
|
||||
generic timeout. Keep the worker limit below the n8n limit if you change either.
|
||||
|
||||
**A turn is atomic, and the worker tells the session so.** The turn ends on the
|
||||
session's final message, and its children end with it: a background `Bash` task
|
||||
is killed, `Monitor` events never arrive, `PushNotification` has nowhere to go,
|
||||
and `ScheduleWakeup` never fires. The session also gets no turn of its own to
|
||||
report back in — the turn's resume URL continues one waiting n8n execution and is
|
||||
then spent, so nothing on the box can post to the thread unprompted. A session
|
||||
that backgrounds a build and signs off with "I'll verify once it finishes" is
|
||||
therefore describing something that cannot happen. The worker states this in
|
||||
`--append-system-prompt` on every turn (`turnContract`), together with a pointer
|
||||
to this file for the box-specific parts. This is only the n8n/Slack path: an
|
||||
interactive session (`pnpm session`, tmux) is long-lived, so background work,
|
||||
monitors and notifications behave normally there.
|
||||
|
||||
### Build and run the app in a session
|
||||
|
||||
The prebuild already installed the dependencies and warmed the build. So a
|
||||
@@ -100,8 +113,10 @@ session rarely needs a cold `pnpm install` or a full `pnpm build`. Both are slow
|
||||
turbo cache and is fast when warm.
|
||||
- To clear stale build outputs after a branch switch, run `pnpm reset`. Add
|
||||
`--full` if that does not clear it.
|
||||
- Give a long build its own turn. Do not chain an install and a full build
|
||||
behind other work in one turn.
|
||||
- Run a long build in the foreground and give it its own turn. Backgrounding it
|
||||
does not help: it is killed when the turn ends (see above). Do not chain an
|
||||
install and a full build behind other work in one turn either — that is what
|
||||
runs into the 25-minute limit.
|
||||
|
||||
## Flaky tools (MCP)
|
||||
|
||||
|
||||
@@ -67,11 +67,53 @@ const TURN_ENV = { ...process.env };
|
||||
if (BOX_ID) TURN_ENV.CODESPACE_NAME = BOX_ID;
|
||||
if (GITHUB_USER) TURN_ENV.GITHUB_USER = GITHUB_USER;
|
||||
|
||||
function runClaude({ message, sessionId, cwd }) {
|
||||
const CODESPACE_DOCS = '.devcontainer/codespaces/README.md';
|
||||
|
||||
// A session cannot be told any of this after its final message, and a system
|
||||
// prompt is not part of the resumed transcript, so send it on every turn. State
|
||||
// the turn's hard limits inline: a session that has to read a file to learn them
|
||||
// can reply before it gets there. Point at the box docs for the rest — they
|
||||
// already cover dev:up, ports, and build cost, and AGENTS.md does not.
|
||||
function turnContract(author) {
|
||||
return [
|
||||
'# Your runtime',
|
||||
'You are one turn of a Slack thread, driven by an n8n workflow that runs you as a headless',
|
||||
'`claude -p` on a GitHub codespace. Your final message is the reply that reaches Slack, so keep',
|
||||
'it short and skip heavy markdown.',
|
||||
author ? `You are replying to ${author}.` : '',
|
||||
'',
|
||||
'# A turn is atomic',
|
||||
'The turn ends when you emit your final message, and everything you started ends with it:',
|
||||
'background Bash tasks are killed, Monitor events never arrive, PushNotification has nowhere to',
|
||||
'go, and ScheduleWakeup never fires. You get no turn of your own afterwards — you cannot speak',
|
||||
'again until a human writes again. So run long work (builds, test suites, restarts) in the',
|
||||
'foreground of this turn and wait for it, or do not start it at all. Never end a turn promising',
|
||||
`to verify, check back, or follow up. Work that will not fit the turn limit of ~${Math.round(
|
||||
TURN_TIMEOUT_MS / 60_000,
|
||||
)} minutes`,
|
||||
'should be split: do the part that fits, then say what to ask for next.',
|
||||
'',
|
||||
'# This box',
|
||||
`You are on codespace ${BOX_ID ?? '(unknown)'}, not a laptop. Before you build, start, or expose`,
|
||||
`the app, read ${CODESPACE_DOCS} ("Build and run the app in a session"). It is box-specific and`,
|
||||
'the repo AGENTS.md does not cover it.',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
function runClaude({ message, sessionId, cwd, author }) {
|
||||
const safeCwd = resolvePath(typeof cwd === 'string' && cwd ? cwd : `${ROOT}/n8n`);
|
||||
if (safeCwd !== ROOT && !safeCwd.startsWith(ROOT + sep))
|
||||
throw new Error(`cwd must be under ${ROOT}`);
|
||||
const args = ['-p', '--output-format', 'json', '--dangerously-skip-permissions'];
|
||||
const args = [
|
||||
'-p',
|
||||
'--output-format',
|
||||
'json',
|
||||
'--dangerously-skip-permissions',
|
||||
'--append-system-prompt',
|
||||
turnContract(typeof author === 'string' ? author : ''),
|
||||
];
|
||||
if (sessionId) args.push('--resume', sessionId);
|
||||
args.push(message);
|
||||
return new Promise((res, rej) => {
|
||||
|
||||
Reference in New Issue
Block a user