start-command 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # start-command
2
2
 
3
+ ## 0.34.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 22c1158: Persist the terminal state of a detached Docker execution and record the container post-mortem. The completion watcher now writes `status`, `exitCode`, `oomKilled` and a real `endTime` back into the store instead of leaving the record `executing` forever, so `--status` stops calling the clock at query time and reporting a different "finish" on every call. `endTime` carries its provenance in `endTimeSource` (`docker-finished-at`, `log-footer`, `observed-at`); a stale record keeps `endTime` empty and records `staleDetectedAt` instead of a fabricated finish time. The single `docker inspect` now also collects `StartedAt`, `FinishedAt` and `State.Error`, and both the detached and the attached path write a post-mortem block for a kept container and a one-line note for a removed one. Signal decoding (`128+n`) is shared by the log and `--status`, so `137` reads as `137 (SIGKILL - 128+9)` everywhere.
8
+
3
9
  ## 0.33.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-command",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "Gamification of coding, execute any command with ability to auto-report issues on GitHub",
5
5
  "main": "src/bin/cli.js",
6
6
  "exports": {
package/src/bin/cli.js CHANGED
@@ -33,6 +33,7 @@ const {
33
33
  } = require('../lib/user-manager');
34
34
  const { handleFailure } = require('../lib/failure-handler');
35
35
  const { ExecutionStore, ExecutionRecord } = require('../lib/execution-store');
36
+ const { reconcileFinalizedRecord } = require('../lib/detached-finalize');
36
37
  const { dispatchQueryCommand, hasQueryMode } = require('../lib/query-commands');
37
38
  const { printVersion } = require('../lib/version');
38
39
  const { createStartBlock, createFinishBlock } = require('../lib/output-blocks');
@@ -469,6 +470,9 @@ async function runWithIsolation(
469
470
  ...buildDockerRuntimeMetadata(options),
470
471
  shell: options.shell,
471
472
  logPath: logFilePath,
473
+ // Lets the detached completion watcher write the terminal state back
474
+ // into this record once the container is gone (issue #170.1).
475
+ executionId: executionRecord ? executionRecord.uuid : null,
472
476
  });
473
477
  } else if (createdUser) {
474
478
  // Run directly as the created user (no isolation environment)
@@ -504,7 +508,11 @@ async function runWithIsolation(
504
508
  executionRecord.complete(exitCode);
505
509
  }
506
510
  try {
507
- store.save(executionRecord);
511
+ store.save(
512
+ mode === 'detached'
513
+ ? reconcileFinalizedRecord(store, executionRecord)
514
+ : executionRecord
515
+ );
508
516
  } catch (err) {
509
517
  if (config.verbose) {
510
518
  console.error(
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Detached completion finalizer (issue #170)
3
+ *
4
+ * A detached Docker session outlives the CLI process that started it: the
5
+ * foreground `start` invocation returns as soon as the container is up, so
6
+ * nobody is left to write the terminal state back into the execution store.
7
+ * The record therefore stayed `executing` forever, with `exitCode: null` and
8
+ * `endTime: null`, and every `--status` query had to re-derive the outcome from
9
+ * scratch — fabricating `endTime` with `new Date()` in the process.
10
+ *
11
+ * The detached completion watcher (see `docker-cleanup.js`) now re-invokes this
12
+ * module once the container is gone, handing over the facts it already read out
13
+ * of a single `docker inspect`. This module is deliberately tiny and
14
+ * dependency-light: it is executed as a short-lived child process on a host
15
+ * whose CLI invocation is long gone, so it must never throw and never block.
16
+ */
17
+
18
+ const path = require('path');
19
+
20
+ const {
21
+ SHELL_VARS,
22
+ describeExitCode,
23
+ normalizeDockerTimestamp,
24
+ } = require('./docker-post-mortem');
25
+ const { shellQuote } = require('./isolation-log-utils');
26
+
27
+ /** Provenance markers for `endTime` (issue #170.2). */
28
+ const END_TIME_SOURCE = {
29
+ /** `docker inspect .State.FinishedAt` — the container's own clock. */
30
+ DOCKER_FINISHED_AT: 'docker-finished-at',
31
+ /** The anchored `Finished:` line `start` itself wrote into the log. */
32
+ LOG_FOOTER: 'log-footer',
33
+ /** No real finish time exists: this is when the end was *observed*. */
34
+ OBSERVED_AT: 'observed-at',
35
+ };
36
+
37
+ /**
38
+ * Persist the terminal state of a detached execution.
39
+ *
40
+ * Purely additive with respect to the observation-vs-verdict discipline of
41
+ * issues #148/#151/#162: `oomKilled` and `exitReason` are recorded as facts
42
+ * next to the exit code, never as a substitute for it.
43
+ *
44
+ * @param {Object} options - Finalization input
45
+ * @param {Object} options.store - An `ExecutionStore`
46
+ * @param {string} options.executionId - UUID (or session name) of the record
47
+ * @param {number|string|null} [options.exitCode] - `.State.ExitCode`
48
+ * @param {boolean|string|null} [options.oomKilled] - `.State.OOMKilled`
49
+ * @param {string|null} [options.startedAt] - `.State.StartedAt`
50
+ * @param {string|null} [options.finishedAt] - `.State.FinishedAt`
51
+ * @param {string|null} [options.containerError] - `.State.Error`
52
+ * @returns {{updated: boolean, reason?: string, record?: Object}} Outcome
53
+ */
54
+ function finalizeDetachedExecution(options = {}) {
55
+ const { store, executionId } = options;
56
+ if (!store || !executionId) {
57
+ return { updated: false, reason: 'missing-arguments' };
58
+ }
59
+
60
+ let record;
61
+ try {
62
+ record = store.get(executionId);
63
+ } catch {
64
+ return { updated: false, reason: 'store-unreadable' };
65
+ }
66
+ if (!record) {
67
+ return { updated: false, reason: 'record-not-found' };
68
+ }
69
+ if (record.status === 'executed' && record.endTime) {
70
+ // Already finalized (e.g. a `--status` query got there first, or the
71
+ // watcher ran twice after a resume). Nothing to correct.
72
+ return { updated: false, reason: 'already-final', record };
73
+ }
74
+
75
+ const described = describeExitCode(options.exitCode);
76
+ record.status = 'executed';
77
+ if (described.code !== null) {
78
+ record.exitCode = described.code;
79
+ } else if (record.exitCode === null || record.exitCode === undefined) {
80
+ record.exitCode = -1;
81
+ }
82
+
83
+ const finishedAt = normalizeDockerTimestamp(options.finishedAt);
84
+ if (finishedAt) {
85
+ record.endTime = new Date(finishedAt).toISOString();
86
+ record.endTimeSource = END_TIME_SOURCE.DOCKER_FINISHED_AT;
87
+ } else {
88
+ // Docker has no finish time for this container (it never started, or it
89
+ // was removed before we could look). Record *when we noticed*, and say so,
90
+ // rather than passing observation time off as a finish time (issue #170.2).
91
+ record.endTime = new Date().toISOString();
92
+ record.endTimeSource = END_TIME_SOURCE.OBSERVED_AT;
93
+ record.observedAt = record.endTime;
94
+ }
95
+
96
+ const startedAt = normalizeDockerTimestamp(options.startedAt);
97
+ if (startedAt) {
98
+ record.containerStartedAt = startedAt;
99
+ }
100
+
101
+ const oomKilled = normalizeBoolean(options.oomKilled);
102
+ if (oomKilled !== null) {
103
+ record.oomKilled = oomKilled;
104
+ }
105
+
106
+ const exitReason = resolveReason(record);
107
+ if (exitReason) {
108
+ record.exitReason = exitReason;
109
+ }
110
+
111
+ const containerError = normalizeContainerError(options.containerError);
112
+ if (containerError) {
113
+ record.options = { ...(record.options || {}), containerError };
114
+ }
115
+
116
+ try {
117
+ store.save(record);
118
+ } catch (err) {
119
+ return { updated: false, reason: `save-failed: ${err.message}`, record };
120
+ }
121
+ return { updated: true, record };
122
+ }
123
+
124
+ /**
125
+ * Derive the `exitReason` hint from the same evidence `--status` would use.
126
+ * Kept in a helper so a failure to read the log never aborts finalization.
127
+ * @param {Object} record - Execution record being finalized
128
+ * @returns {string|null} Reason hint, or null
129
+ */
130
+ function resolveReason(record) {
131
+ try {
132
+ const { resolveExitReason } = require('./exit-reason');
133
+ const {
134
+ FATAL_MARKER_TAIL_BYTES,
135
+ readLogTail,
136
+ } = require('./isolation-log-utils');
137
+ const logTail = record.logPath
138
+ ? readLogTail(record.logPath, FATAL_MARKER_TAIL_BYTES)
139
+ : null;
140
+ return resolveExitReason({
141
+ exitCode: record.exitCode,
142
+ logTail,
143
+ oomKilled: record.oomKilled,
144
+ });
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * @param {boolean|string|null|undefined} value - Raw shell-provided flag
152
+ * @returns {boolean|null} Parsed flag, or null when it carries no information
153
+ */
154
+ function normalizeBoolean(value) {
155
+ if (typeof value === 'boolean') {
156
+ return value;
157
+ }
158
+ if (value === 'true') {
159
+ return true;
160
+ }
161
+ if (value === 'false') {
162
+ return false;
163
+ }
164
+ return null;
165
+ }
166
+
167
+ /**
168
+ * @param {string|null|undefined} value - Raw `.State.Error`
169
+ * @returns {string|null} Error text, or null when docker reported none
170
+ */
171
+ function normalizeContainerError(value) {
172
+ if (typeof value !== 'string') {
173
+ return null;
174
+ }
175
+ const trimmed = value.trim();
176
+ if (trimmed === '' || trimmed === '<no value>' || trimmed === '(none)') {
177
+ return null;
178
+ }
179
+ return trimmed;
180
+ }
181
+
182
+ /**
183
+ * Reconcile an in-memory record with one the detached watcher already
184
+ * finalized.
185
+ *
186
+ * A container that exits almost immediately can be finalized by the watcher
187
+ * before the foreground `start` invocation writes its own last update. Saving
188
+ * the stale in-memory copy would resurrect `status: executing` and throw the
189
+ * terminal facts away, so the stored record wins and only the fields the
190
+ * foreground process learned (its `options`, e.g. `containerId`) are merged in.
191
+ *
192
+ * @param {Object} store - An `ExecutionStore`
193
+ * @param {Object} record - The in-memory record about to be saved
194
+ * @returns {Object} The record to save
195
+ */
196
+ function reconcileFinalizedRecord(store, record) {
197
+ try {
198
+ const stored = store.get(record.uuid);
199
+ if (!stored || stored.status !== 'executed' || !stored.endTime) {
200
+ return record;
201
+ }
202
+ stored.options = { ...(stored.options || {}), ...(record.options || {}) };
203
+ return stored;
204
+ } catch {
205
+ return record;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Shell fragment that hands the inspected docker facts to this module.
211
+ *
212
+ * Runs with the same runtime that started the session, inherits
213
+ * `START_APP_FOLDER`/`START_DISABLE_TRACKING` from the detached watcher, and is
214
+ * suffixed with `|| true` so a finalization failure can never abort the
215
+ * watcher's remaining cleanup work.
216
+ *
217
+ * @param {string} executionId - UUID of the record to finalize
218
+ * @returns {string} POSIX shell fragment
219
+ */
220
+ function buildDetachedFinalizeSnippet(executionId) {
221
+ const v = SHELL_VARS;
222
+ return (
223
+ `${shellQuote(process.execPath)} ${shellQuote(__filename)} ` +
224
+ `${shellQuote(executionId)} "$${v.exit}" "$${v.oom}" ` +
225
+ `"$${v.started}" "$${v.finished}" "$${v.error}" ` +
226
+ `>/dev/null 2>&1 || true`
227
+ );
228
+ }
229
+
230
+ /**
231
+ * Entry point used by the detached watcher:
232
+ * <runtime> detached-finalize.js <uuid> <exit> <oom> <started> <finished> <error>
233
+ * Always exits 0 — a bookkeeping failure must never turn into a visible error
234
+ * in a log the user is reading for the command's own output.
235
+ * @param {string[]} argv - Positional arguments
236
+ * @returns {void}
237
+ */
238
+ function main(argv) {
239
+ const [
240
+ executionId,
241
+ exitCode,
242
+ oomKilled,
243
+ startedAt,
244
+ finishedAt,
245
+ containerError,
246
+ ] = argv;
247
+ if (!executionId || process.env.START_DISABLE_TRACKING === 'true') {
248
+ return;
249
+ }
250
+ try {
251
+ const { ExecutionStore } = require('./execution-store');
252
+ const store = new ExecutionStore(
253
+ process.env.START_APP_FOLDER
254
+ ? { appFolder: path.resolve(process.env.START_APP_FOLDER) }
255
+ : {}
256
+ );
257
+ finalizeDetachedExecution({
258
+ store,
259
+ executionId,
260
+ exitCode,
261
+ oomKilled,
262
+ startedAt,
263
+ finishedAt,
264
+ containerError,
265
+ });
266
+ } catch {
267
+ // Deliberately silent: see the exit-0 contract above.
268
+ }
269
+ }
270
+
271
+ if (require.main === module) {
272
+ main(process.argv.slice(2));
273
+ }
274
+
275
+ module.exports = {
276
+ END_TIME_SOURCE,
277
+ buildDetachedFinalizeSnippet,
278
+ finalizeDetachedExecution,
279
+ main,
280
+ reconcileFinalizedRecord,
281
+ };
@@ -7,6 +7,16 @@ const {
7
7
  shellQuote,
8
8
  } = require('./isolation-log-utils');
9
9
  const { resolveMemoryExhaustion } = require('./exit-reason');
10
+ const {
11
+ buildDockerPostMortemSnippet,
12
+ buildDockerRemovalNoteSnippet,
13
+ buildDockerStateSnippet,
14
+ DOCKER_STATE_INSPECT_FORMAT,
15
+ formatContainerPostMortem,
16
+ formatContainerRemovalNote,
17
+ normalizeDockerTimestamp,
18
+ } = require('./docker-post-mortem');
19
+ const { buildDetachedFinalizeSnippet } = require('./detached-finalize');
10
20
 
11
21
  const DOCKER_CONTAINER_CLEANUP_POLICY = {
12
22
  DEFAULT: 'default',
@@ -91,27 +101,85 @@ function appendDockerContainerCleanupPolicyMessage(
91
101
  return `${message}\nContainer will be removed after command completes.`;
92
102
  }
93
103
 
94
- function readDockerContainerOomKilled(containerName) {
95
- const result = spawnSync(
96
- getDockerCommand(),
97
- ['inspect', '-f', '{{.State.OOMKilled}}', containerName],
98
- getDockerSpawnOptions({
99
- encoding: 'utf8',
100
- env: process.env,
101
- stdio: ['pipe', 'pipe', 'pipe'],
102
- })
103
- );
104
- if (result.error || result.status !== 0) {
104
+ /**
105
+ * Read a finished container's post-mortem facts in a single `docker inspect`.
106
+ *
107
+ * The attached path only ever inspected `State.OOMKilled`, which left its
108
+ * "Container kept for investigation" message unable to say *why* the container
109
+ * died — the same gap the detached watcher had (issue #171.1). Reading the
110
+ * facts once, before any `docker rm`, also makes them available to the removal
111
+ * path, where the container is about to stop existing (issue #171.3).
112
+ *
113
+ * @param {string} containerName - Container to inspect
114
+ * @returns {object|null} Facts accepted by the `docker-post-mortem` formatters,
115
+ * or null when the container could not be inspected at all
116
+ */
117
+ function readDockerContainerState(containerName) {
118
+ const inspect = (format) => {
119
+ const result = spawnSync(
120
+ getDockerCommand(),
121
+ ['inspect', '-f', format, containerName],
122
+ getDockerSpawnOptions({
123
+ encoding: 'utf8',
124
+ env: process.env,
125
+ stdio: ['pipe', 'pipe', 'pipe'],
126
+ })
127
+ );
128
+ if (result.error || result.status !== 0) {
129
+ return null;
130
+ }
131
+ return String(result.stdout || '').trim();
132
+ };
133
+ const state = inspect(DOCKER_STATE_INSPECT_FORMAT);
134
+ if (state === null) {
105
135
  return null;
106
136
  }
107
- const value = String(result.stdout || '').trim();
108
- if (value === 'true') {
109
- return true;
137
+ const [exitCode, oomKilled, startedAt, finishedAt] = state.split(/\s+/);
138
+ // `State.Error` is free-form text that may contain spaces, so it cannot ride
139
+ // along in the whitespace-separated template above.
140
+ const error = inspect('{{.State.Error}}');
141
+ const parsedExit = Number.parseInt(exitCode, 10);
142
+ return {
143
+ containerName,
144
+ exitCode: Number.isFinite(parsedExit) ? parsedExit : null,
145
+ oomKilled:
146
+ oomKilled === 'true' ? true : oomKilled === 'false' ? false : null,
147
+ startedAt: normalizeDockerTimestamp(startedAt),
148
+ finishedAt: normalizeDockerTimestamp(finishedAt),
149
+ error: error || null,
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Write the post-mortem for an attached run into its log and return the same
155
+ * text for the console message.
156
+ *
157
+ * Kept and removed containers both get their facts recorded — a kept container
158
+ * gets the full block, a removed one the single line — so the log of an
159
+ * attached run carries exactly what the detached watcher writes (issues #171.2
160
+ * and #171.3).
161
+ *
162
+ * @param {object} params - Container name, inspected state, log path, and
163
+ * whether the container was removed
164
+ * @returns {string} Message lines to append, or '' when nothing is known
165
+ */
166
+ function recordAttachedDockerPostMortem({
167
+ containerName,
168
+ state,
169
+ logPath = null,
170
+ removed = false,
171
+ }) {
172
+ if (!state) {
173
+ return '';
110
174
  }
111
- if (value === 'false') {
112
- return false;
175
+ const facts = { ...state, containerName };
176
+ const text = removed
177
+ ? formatContainerRemovalNote(facts)
178
+ : formatContainerPostMortem(facts);
179
+ if (logPath) {
180
+ appendLogFile(logPath, `\n${text}`);
113
181
  }
114
- return null;
182
+ return `\n${text.trimEnd()}`;
115
183
  }
116
184
 
117
185
  function readDockerContainerStatus(containerName) {
@@ -231,37 +299,68 @@ function buildSuccessfulNonOomCondition() {
231
299
  );
232
300
  }
233
301
 
234
- function buildDetachedDockerCompletionScript(containerName, policy, logPath) {
302
+ /**
303
+ * Build the POSIX shell run by the detached completion watcher.
304
+ *
305
+ * The watcher is the only observer left once `start --detached` returns, so it
306
+ * carries three responsibilities that used to be missing or deferred:
307
+ * 1. it reads the container's post-mortem facts out of a single
308
+ * `docker inspect` (issue #171.1);
309
+ * 2. it writes those facts into the log on every path — kept *and* removed —
310
+ * so an operator reading the log never has to reconstruct them from a
311
+ * container that no longer exists (issues #171.2, #171.3);
312
+ * 3. it hands the same facts to the finalizer so the execution record stops
313
+ * being `executing` forever (issue #170.1).
314
+ *
315
+ * @param {string} containerName - Docker container name
316
+ * @param {string} policy - One of DOCKER_CONTAINER_CLEANUP_POLICY
317
+ * @param {string|null} logPath - Log file to append to, or null
318
+ * @param {string|null} [executionId] - Execution UUID to finalize, or null
319
+ * @returns {string} The shell script
320
+ */
321
+ function buildDetachedDockerCompletionScript(
322
+ containerName,
323
+ policy,
324
+ logPath,
325
+ executionId = null
326
+ ) {
235
327
  const quotedName = shellQuote(containerName);
236
328
  const parts = [];
237
329
 
238
330
  if (logPath) {
239
331
  const quotedLogPath = shellQuote(logPath);
240
- parts.push(`docker logs -f ${quotedName} >> ${quotedLogPath} 2>&1`);
241
- parts.push(
242
- `__start_command_state=$(docker inspect -f '{{.State.ExitCode}} {{.State.OOMKilled}}' ${quotedName} 2>/dev/null || printf '%s' '-1 false')`
332
+ const removalNote = buildDockerRemovalNoteSnippet(
333
+ containerName,
334
+ quotedLogPath
335
+ );
336
+ const postMortem = buildDockerPostMortemSnippet(
337
+ containerName,
338
+ quotedLogPath
243
339
  );
244
- parts.push('__start_command_exit=${__start_command_state%% *}');
245
- parts.push('__start_command_oom=${__start_command_state##* }');
340
+ const remove = `docker rm -f ${quotedName} >> ${quotedLogPath} 2>&1 || true; ${removalNote}`;
341
+ const keep = `${postMortem}; ${buildDockerKeptLogSnippet(containerName, quotedLogPath)}`;
342
+
343
+ parts.push(`docker logs -f ${quotedName} >> ${quotedLogPath} 2>&1`);
344
+ parts.push(buildDockerStateSnippet(containerName));
246
345
  if (policy === DOCKER_CONTAINER_CLEANUP_POLICY.ALWAYS) {
247
- parts.push(`docker rm -f ${quotedName} >> ${quotedLogPath} 2>&1 || true`);
346
+ parts.push(remove);
248
347
  } else if (
249
348
  policy === DOCKER_CONTAINER_CLEANUP_POLICY.DEFAULT ||
250
349
  policy === DOCKER_CONTAINER_CLEANUP_POLICY.KEEP_ON_FAIL
251
350
  ) {
252
- const successCondition = buildSuccessfulNonOomCondition();
253
351
  parts.push(
254
- `if ${successCondition}; then docker rm -f ${quotedName} >> ${quotedLogPath} 2>&1 || true; else ${buildDockerKeptLogSnippet(containerName, quotedLogPath)}; fi`
352
+ `if ${buildSuccessfulNonOomCondition()}; then ${remove}; else ${keep}; fi`
255
353
  );
354
+ } else {
355
+ // KEEP: the container is never removed, so the log used to end with the
356
+ // raw command output and nothing else. The post-mortem is exactly the
357
+ // information an operator needs before deciding what to do with it.
358
+ parts.push(keep);
256
359
  }
257
360
  parts.push(`${createShellLogFooterSnippet()} >> ${quotedLogPath}`);
258
361
  } else {
259
362
  parts.push(`docker wait ${quotedName} >/dev/null 2>&1`);
260
- parts.push(
261
- `__start_command_state=$(docker inspect -f '{{.State.ExitCode}} {{.State.OOMKilled}}' ${quotedName} 2>/dev/null || printf '%s' '-1 false')`
262
- );
263
- parts.push('__start_command_exit=${__start_command_state%% *}');
264
- parts.push('__start_command_oom=${__start_command_state##* }');
363
+ parts.push(buildDockerStateSnippet(containerName));
265
364
  if (policy === DOCKER_CONTAINER_CLEANUP_POLICY.ALWAYS) {
266
365
  parts.push(`docker rm -f ${quotedName} >/dev/null 2>&1 || true`);
267
366
  } else if (
@@ -274,13 +373,39 @@ function buildDetachedDockerCompletionScript(containerName, policy, logPath) {
274
373
  }
275
374
  }
276
375
 
376
+ if (executionId) {
377
+ // Last, so the record is only marked terminal once the log is complete.
378
+ parts.push(buildDetachedFinalizeSnippet(executionId));
379
+ }
380
+
277
381
  return parts.join('; ');
278
382
  }
279
383
 
280
- function startDetachedDockerCompletionWatcher(containerName, policy, logPath) {
384
+ /**
385
+ * Start the detached completion watcher.
386
+ * @param {string} containerName - Docker container name
387
+ * @param {string} policy - One of DOCKER_CONTAINER_CLEANUP_POLICY
388
+ * @param {string|null} logPath - Log file to append to, or null
389
+ * @param {string|null} [executionId] - Execution UUID to finalize, or null
390
+ * @returns {void}
391
+ */
392
+ function startDetachedDockerCompletionWatcher(
393
+ containerName,
394
+ policy,
395
+ logPath,
396
+ executionId = null
397
+ ) {
281
398
  const watcher = spawn(
282
399
  'sh',
283
- ['-c', buildDetachedDockerCompletionScript(containerName, policy, logPath)],
400
+ [
401
+ '-c',
402
+ buildDetachedDockerCompletionScript(
403
+ containerName,
404
+ policy,
405
+ logPath,
406
+ executionId
407
+ ),
408
+ ],
284
409
  {
285
410
  detached: true,
286
411
  stdio: 'ignore',
@@ -327,8 +452,9 @@ module.exports = {
327
452
  shouldCleanupDockerContainer,
328
453
  getDockerContainerCleanupInstructions,
329
454
  appendDockerContainerCleanupPolicyMessage,
330
- readDockerContainerOomKilled,
455
+ readDockerContainerState,
331
456
  readDockerContainerStatus,
457
+ recordAttachedDockerPostMortem,
332
458
  removeDockerContainer,
333
459
  buildDetachedDockerCompletionScript,
334
460
  startDetachedDockerCompletionWatcher,