# SSH cancellation के बाद remote process cleanup कैसे काम करता है?

रद्द किया गया agent run और रुकी हुई remote command एक ही घटना नहीं हैं। स्थानीय process साफ़ तौर पर बंद हो सकता है, जबकि SSH transport अभी जीवित हो। Transport गायब हो सकता है, जबकि remote shell चलता रहे। Shell समाप्त हो सकता है, जबकि उसके children किसी दूसरे process group में चलते रहें। यदि आप इन घटनाओं को एक ही «cancelled» status में मिला देंगे, तो किसी दिन database migration, package install, test worker या deployment helper agent के «रुकने» की सूचना देने के बाद भी चलता मिलेगा।

समाधान केवल कोई चालाक signal trap नहीं है। आपको ऐसा cancellation contract चाहिए जो remote run का नाम तय करे, उसके descendants के चारों ओर समाप्त किए जा सकने वाला boundary बनाए, बाधित run की जाँच के लिए पर्याप्त output बचाए और controller के गायब हो जाने पर remote host की जिम्मेदारी स्पष्ट करे। Agent को side effects वाली commands चलाने देने से पहले इस contract को बनाकर अच्छी तरह जाँचें।

## SSH cancellation चार अलग-अलग चरणों से गुजरता है

Cancellation request को चार सीमाएँ पार करनी होती हैं: agent रुकने का निर्णय लेता है, local supervisor अपने SSH client को रोकता या signal करता है, SSH protocol channel event या signal पहुँचाता है और remote host उस घटना पर कार्रवाई करता है। हर चरण स्वतंत्र रूप से विफल हो सकता है।

RFC 4254 इन अवधारणाओं को अलग रखता है। इसमें channel close message और `TERM`, `INT` तथा `HUP` जैसे नामों के लिए अलग `signal` channel request परिभाषित हैं। Channel close transport event है। इसका अर्थ «हर remote descendant को SIGTERM भेजो» नहीं है। RFC यह भी कहता है कि close से पहले भेजा गया data, संभव हो तो, पहुँचाया जाना चाहिए। «संभव हो तो» तब बहुत व्यापक अर्थ रखता है जब laptop sleep में चला जाए, network route टूट जाए या local process को force-quit किया जाए।

यह अंतर एक आम खराब design को सामने लाता है:

1. Agent `ssh host long-command` शुरू करता है।
2. User cancel दबाता है।
3. Agent runner अपने local child process को समाप्त कर देता है।
4. UI job को cancelled दिखाती है।
5. `long-command`, या उसका कोई child, host पर चलता रहता है।

पाँचवीं पंक्ति कोई दुर्लभ स्थिति नहीं है। जब server के पास command समाप्त करने का कोई कारण नहीं होता, या connection टूटने से पहले command session से अलग हो जाती है, तो यही सामान्य परिणाम है।

एक अच्छा cancellation contract साफ़ बताता है कि local side क्या करने की कोशिश करेगा और remote side की जिम्मेदारी क्या है:

- Launcher random run ID के साथ एक पहचान योग्य remote run बनाता है।
- Remote wrapper workload को अपने process group या session में शुरू करता है।
- सामान्य cancel पर उस process group को `TERM` भेजा जाता है और परिणाम दर्ज होता है।
- तय grace period के बाद ही wrapper `KILL` तक जाता है।
- जब controller वापस नहीं आता, तो remote deadline या lease काम समाप्त करती है।
- Output और अंतिम status SSH stream के बाद भी सुरक्षित रहते हैं।

जब तक आपको दो में से एक परिणाम न मिल जाए, task को cancelled न कहें: confirmed final remote record या स्पष्ट «state unknown» परिणाम। Connection loss के बाद झूठी निश्चितता incident response को धीमा करती है, क्योंकि सभी लोग गलत आधार से शुरुआत करते हैं।

## Children साफ़ करने के लिए remote PID पर्याप्त नहीं है

Remote shell PID को समाप्त करना तभी सुरक्षित है जब shell कभी fork न करे, pipeline न शुरू करे, background process न चलाए और ऐसा tool न बुलाए जो helpers बनाए। Commands का यह समूह बहुत छोटा है।

इस सामान्य remote command को देखें:

```sh
build-assets | tee build.log &
wait
```

Shell का एक PID है, लेकिन pipeline में कई processes हैं। Shell समाप्त होने के बाद भी `tee` log लिख सकता है। Compiler worker processes शुरू कर सकता है। Package manager काम किसी service को सौंप सकता है। यदि आप `kill -TERM "$shell_pid"` भेजते हैं, तो बड़े group के केवल एक सदस्य को समाप्त करेंगे और बाकी के बारे में बहुत कम जान पाएँगे।

Short-lived remote run के लिए process groups cancellation की सही इकाई देते हैं। Linux में हर process किसी process group में और हर process group किसी session में होता है। Terminal-generated signals foreground process group को जाते हैं, इसलिए terminal behavior वास्तविकता से अधिक जादुई लग सकता है। Linux `setpgid(2)` documentation यह भी स्पष्ट करती है कि child अपने parent का process group inherit करता है, जब तक कुछ उसे बदल न दे।

Agent-controlled command के लिए workload का नया session बनाएँ। Session leader का PID सामान्यतः उसके PGID और SID के बराबर होता है। तब `kill` में negative PID process group को संबोधित करता है:

```sh
kill -TERM -- -"$pgid"
```

शुरुआती minus sign process समाप्त करने और process group समाप्त करने के बीच का अंतर है। `--` भी महत्वपूर्ण है। यह malformed value को option की तरह parse होने से रोकता है।

यह मानकर न चलें कि workload PID ही उसका process group ID है। Launch के समय इसकी जाँच करें। Shell wrapper, service manager या `setpgid` बुलाने वाला program tree बदल सकता है। आपके test kit में रखने लायक सबसे छोटी inspection command यह है:

```sh
ps -o pid=,ppid=,pgid=,sid=,stat=,etime=,command= -p "$pid"
```

सामान्य output इस तरह दिखता है:

```text
24182  24177  24182  24182 Ss       00:03 bash ./worker.sh /tmp/agent-runs/6c4...
```

यहाँ PID, PGID और SID एक जैसे हैं। इससे प्रमाण मिलता है कि `kill -TERM -- -24182` अपेक्षित boundary को target करता है। यदि PGID run record से मेल नहीं खाता, तो अनुमान लगाने के बजाय launch विफल करें।

Process group की भी सीमाएँ हैं। कोई child `setsid` बुला सकता है, container runtime process को कहीं और भेज सकता है और workload service manager से group के बाहर कुछ चलाने को कह सकता है। यह व्यवहार कभी-कभी उचित होता है, लेकिन इसका अर्थ है कि cancellation guarantee उस handoff पर समाप्त हो गई। Detached work को अलग job type मानें, जिसकी अपनी identity, stop operation और audit record हो।

## Cancellation wrapper को वास्तविक cleanup path चाहिए

Remote shell wrapper को workload PID का मालिक होना चाहिए, expected termination signals को trap करना चाहिए, workload group को target करना चाहिए, थोड़ी देर प्रतीक्षा करनी चाहिए और final record लिखना चाहिए। `pkill command-name`, ढीली process list या account के हर process को समाप्त करने जैसी shortcuts न अपनाएँ। ये तब तक काम करती हैं जब तक दो agent runs एक user साझा न करें, hostname बदल न जाए या command name किसी और के काम से न मिल जाए।

यह Linux-oriented fixture जानबूझकर सरल है। यह सुरक्षित run directory बनाता है, एक workload को नए session में शुरू करता है, output files में लिखता है और wrapper को `TERM`, `INT` या `HUP` मिलने पर workload group समाप्त करता है।

```bash
#!/usr/bin/env bash
set -Eeuo pipefail

run_id=${1:?run ID required}
shift
run_dir="${HOME}/.agent-runs/${run_id}"
umask 077
mkdir -p "$run_dir"

child_pid=""
child_pgid=""
finished=0

write_status() {
  local state=$1
  local code=${2:-}
  local tmp="$run_dir/status.tmp"
  printf '{"run_id":"%s","state":"%s","exit_code":"%s"}\n' \
    "$run_id" "$state" "$code" >"$tmp"
  mv "$tmp" "$run_dir/status.json"
}

stop_group() {
  if [[ -z ${child_pgid:-} ]]; then
    return
  fi

  kill -TERM -- "-$child_pgid" 2>/dev/null || true
  for _ in 1 2 3 4 5; do
    if ! kill -0 -- "-$child_pgid" 2>/dev/null; then
      return
    fi
    sleep 1
  done
  kill -KILL -- "-$child_pgid" 2>/dev/null || true
}

cancel() {
  local signal=$1
  trap - TERM INT HUP
  write_status "cancelling:$signal"
  stop_group
  wait "$child_pid" 2>/dev/null || true
  write_status "cancelled:$signal"
  finished=1
  exit 143
}

trap 'cancel TERM' TERM
trap 'cancel INT' INT
trap 'cancel HUP' HUP

write_status "starting"
setsid "$@" >"$run_dir/stdout.log" 2>"$run_dir/stderr.log" &
child_pid=$!
child_pgid=$(ps -o pgid= -p "$child_pid" | tr -d ' ')

if [[ "$child_pgid" != "$child_pid" ]]; then
  printf 'unexpected PGID for %s: %s\n' "$child_pid" "$child_pgid" \
    >"$run_dir/stderr.log"
  kill -TERM "$child_pid" 2>/dev/null || true
  write_status "launch_failed"
  exit 70
fi

printf '%s\n' "$child_pid" >"$run_dir/pid"
printf '%s\n' "$child_pgid" >"$run_dir/pgid"
write_status "running"

set +e
wait "$child_pid"
code=$?
set -e

if [[ $finished -eq 0 ]]; then
  write_status "finished" "$code"
fi
exit "$code"
```

यह wrapper एक खास failure रोकता है: cancellation signal wrapper तक पहुँचता है, लेकिन wrapper केवल खुद को समाप्त करता है और workload को छोड़ देता है। यह जानबूझकर अलग किए गए descendants को रोकने का वादा नहीं करता और उसे ऐसा वादा करना भी नहीं चाहिए।

Linux `setsid(2)` manual के अनुसार `setsid()` नया session बनाता है और caller को नए process group का leader बनाता है, शुरुआत में उसके पास controlling terminal नहीं होता। util-linux का `setsid` command program को उस नए session में चलाता है और ज़रूरत पड़ने पर fork करता है। इसलिए यह command run के लिए व्यावहारिक boundary है, कोई जादुई cleanup switch नहीं।

Wrapper को सीमित रखें। उसका काम launch, record, stop और report करना है। Business logic उसमें न छिपाएँ। Workload की अपनी transaction handling, temporary-file cleanup और idempotency rules होनी चाहिए।

## साफ़ transport disconnect cleanup की गारंटी नहीं है

SSH users अक्सर terminal test से बहुत अधिक निष्कर्ष निकालते हैं। वे PTY के साथ command चलाते हैं, terminal बंद करते हैं, `SIGHUP` के बाद process को समाप्त होते देखते हैं और मान लेते हैं कि disconnect cleanup काम करता है। फिर agent बिना PTY वाला non-interactive SSH channel इस्तेमाल करता है और व्यवहार बदल जाता है।

PTY terminal semantics बनाता है। Terminal hangup से `SIGHUP` पहुँच सकता है, लेकिन यह controlling terminals और foreground process groups पर लागू परिस्थितियों पर निर्भर करता है। Linux manual `SIGHUP` को controlling terminal के hangup या controlling process की मृत्यु के रूप में बताता है। इसका अर्थ यह नहीं कि हर SSH disconnection SSH से शुरू किए गए हर process को signal भेजता है।

Agents के लिए non-interactive SSH सामान्यतः बेहतर default है, क्योंकि इससे output साफ़ रहता है और shell startup surprises कम होते हैं। इससे terminal behavior पर आकस्मिक निर्भरता भी हटती है। PTY तभी इस्तेमाल करें जब remote program को इसकी आवश्यकता हो, जैसे कोई पुराना installer जो इसके बिना चलने से इनकार करे। तब यह भी दर्ज करें कि PTY command के behavior का हिस्सा है और इसकी अलग जाँच करें।

तीन disconnect cases को अलग नाम देना उपयोगी है:

### Client जानबूझकर cancel भेजता है

Local supervisor के पास अभी live connection है और वह protocol signal भेज सकता है, या recorded PGID को signal करने वाली अलग authenticated control command खोल सकता है। यह सबसे अच्छी स्थिति है। Remote wrapper `TERM` प्राप्त करता है, cleanup करता है और `cancelled:TERM` लिखता है।

जब तक आपने अपने इस्तेमाल किए गए client library और invocation की जाँच न कर ली हो, केवल यह मानकर न चलें कि SSH client को local `SIGINT` मिलने पर वह यही काम करेगा। Terminal client, embedded SSH library और MCP tool local cancellation को अलग तरह से map कर सकते हैं। कोई socket बंद करेगा, कोई local process समाप्त करेगा और कोई SSH `signal` request भेज सकता है। ये उस interface के अलग implementations हैं जिसे users «cancel» कहते हैं।

### Local client crash होता है या network खो जाता है

Remote command चलती रह सकती है। Server temporary routing problem और ऐसे user में अंतर नहीं कर सकता जो चाहता है कि काम चलता रहे, जब तक आपका protocol उसे यह जानकारी न दे। Remote lease इसका ईमानदार समाधान है।

Launch के समय remote run directory में `deadline_epoch` लिखें। Run अधिकृत रहने तक local supervisor उसे renew करे। Remote watchdog उसकी जाँच करे और expire होने के बाद उसी process-group cleanup path को बुलाए। Lease duration operation के अनुसार चुनें। पाँच मिनट की lease shell command के लिए ठीक हो सकती है, लेकिन लंबे और सामान्य quiet phases वाले build के लिए जोखिमभरी हो सकती है।

### Remote host विफल होता है या reboot करता है

आप process और final status दोनों खो सकते हैं। केवल SSH connection समाप्त होने के कारण «cancelled» या «completed» report न करें। बाद में reconciliation host के journal, deployment state, lock record या application-specific result को पढ़े, तब तक run को unknown रखें।

कठिनाई status word emit करने में नहीं है। कठिनाई ऐसे status word को emit करने से इनकार करना है जिसे आपका system support नहीं कर सकता।

## Partial output observation साबित करता है, completion नहीं

Stream केवल यह बताता है कि «client को अब तक कौन से bytes मिले?» यह नहीं बताता कि «remote command ने पीछे कौन-सी state छोड़ी?» यह गलती तब दिखती है जब remote program आखिरी file flush करने से पहले `done` लिख देता है, या command पूरी होने के बाद लेकिन SSH exit status client तक पहुँचने से पहले network टूट जाता है।

दो records रखें:

- `stdout.log` और `stderr.log` workload के लिखते समय diagnostic output रखते हैं।
- `status.json` छोटा final record है, जिसे wrapper exit देखने या cancellation संभालने के बाद atomic रूप से लिखता है।

Wrapper में `mv` महत्वपूर्ण है। उसी directory में temporary status file लिखें और फिर उसे सही नाम पर rename करें। Readers को या तो पिछली पूरी file दिखेगी या नई पूरी file। उन्हें आधे JSON document को parse करके मनगढ़ंत result नहीं बनाना चाहिए।

Output के लिए भी rules चाहिए। stdout terminal के बजाय file होने पर command output को बहुत buffer कर सकती है। यदि progress महत्वपूर्ण है, तो workload से stderr पर स्पष्ट line-oriented status लिखवाएँ या application-level progress file इस्तेमाल करें। हर command के लिए PTY देकर buffering की समस्या न सुलझाएँ। इससे behavior बदलता है और stdout तथा stderr मिल सकते हैं, जिससे audit और failure analysis खराब होते हैं।

Agent UI या journal में इन स्थितियों को अलग रखें:

| Remote record | Stream state | अर्थ |
| --- | --- | --- |
| `finished`, exit code मौजूद | complete | Wrapper ने सामान्य completion देखी। |
| `cancelled:TERM` | अचानक समाप्त हो सकता है | Wrapper ने cancellation शुरू की और अपना group रोक दिया। |
| केवल `cancelling:TERM` | disconnected | Cleanup शुरू हुआ, लेकिन final record नहीं देखा गया। Reconcile करें। |
| केवल `running`, lease valid | disconnected | Work अभी चल रहा हो सकता है। बिना जाँच दोबारा शुरू न करें। |
| कोई usable record नहीं | disconnected | State unknown है। Relaunch से पहले side effects जाँचें। |

इन logs में access tokens, unredacted configuration dumps या credentials न रखें। SSH credential injection private key को agent से दूर रख सकता है, लेकिन remote command अपने environment या configuration से पढ़े गए secrets फिर भी print कर सकती है। Output retention command design का हिस्सा है, बाद में जोड़ी जाने वाली चीज़ नहीं।

## एक सोई हुई shell के बजाय process trees जाँचें

`trap 'exit' TERM; sleep 600` कमजोर cancellation test है। यह साबित करता है कि एक foreground shell एक signal पा सकती है। यह children, process groups, देर से होने वाले cleanup, output persistence या गलत समय पर गायब होने वाली remote shell को test नहीं करता।

ऐसा workload इस्तेमाल करें जो दिखाई देने वाला process tree बनाए और हर signal दर्ज करे। इसे disposable Linux host पर `worker.sh` के रूप में save करें:

```bash
#!/usr/bin/env bash
set -Eeuo pipefail
run_dir=${1:?run directory required}

note() {
  printf '%s pid=%s pgid=%s %s\n' \
    "$(date +%s)" "$$" "$(ps -o pgid= -p $$ | tr -d ' ')" "$1" \
    >>"$run_dir/worker.log"
}

trap 'note TERM; exit 143' TERM
trap 'note INT; exit 130' INT
trap 'note HUP; exit 129' HUP

(
  trap 'note grandchild_TERM; exit 143' TERM
  trap 'note grandchild_HUP; exit 129' HUP
  while :; do
    note grandchild_tick
    sleep 1
  done
) &
grandchild=$!

note "started grandchild=$grandchild"
while :; do
  note parent_tick
  sleep 1
done
```

इसे random run ID के साथ wrapper से चलाएँ। दूसरी SSH session में process tree और run directory देखें:

```sh
run_id=cancel-test-$(date +%s)
ssh host.example './remote-wrapper.sh '"$run_id"' ./worker.sh "$HOME/.agent-runs/'"$run_id"'"'
```

वास्तविक launcher में exact quoting अलग होगी। यह ठीक है। जो नहीं बदलना चाहिए वह test evidence है: remote run ID, wrapper PID, workload PGID, log locations और final status ज़रूर चाहिए।

फिर इन failure paths को एक-एक करके test करें:

1. Wrapper PID को `TERM` भेजें। पुष्टि करें कि parent और grandchild दोनों ने termination दर्ज की और `ps` को recorded PGID में कोई process नहीं मिला।
2. Workload PID को सीधे `TERM` भेजें। पुष्टि करें कि उसके child behavior को सुरक्षित मानकर नहीं चला जा सकता। इससे समझ आता है कि wrapper group को target क्यों करता है।
3. Remote signal भेजे बिना local SSH client को समाप्त करें। पुष्टि करें कि remote lease expire होने तक workload सक्रिय रहता है। यदि वह तुरंत रुकता है, तो इसका कारण, जैसे PTY hangup behavior, दर्ज करें और इसे सार्वभौमिक परिणाम न मानें।
4. Wrapper के `cancelling:TERM` लिखने के बाद लेकिन final record लिखने से पहले disconnect करें। पुष्टि करें कि reconciliation अधूरी observation और नए running job में अंतर कर सकती है।
5. एक ही account के अंतर्गत दो runs शुरू करें, एक को cancel करें और साबित करें कि दूसरा चलता रहे। इससे खतरनाक broad `pkill` और account-wide cleanup logic पकड़ी जाती है।

Testing के दौरान `ps` और `pgrep -a -g "$pgid"` इस्तेमाल करें और grace period के बाद फिर जाँचें। Process table देखने के बाद ही remote status file और logs जाँचें। यदि status record «cancelled» कहता है, लेकिन workers अभी जीवित हैं, तो यह supervisor का bug है, मामूली reporting mismatch नहीं।

## Parent-death signals नियंत्रित worker के भीतर ही मदद करते हैं

Linux में `PR_SET_PDEATHSIG` उपलब्ध है। इससे कोई process kernel से कह सकता है कि उसे बनाने वाला thread समाप्त होने पर उसे signal भेजा जाए। यह तब उपयोगी हो सकता है जब आपके नियंत्रण में छोटा native helper हो, जो तुरंत child बनाता है और helper समाप्त होने पर child को भी रोकना चाहता है। सामान्य स्थितियों में यह setting `execve` के बाद बनी रहती है, लेकिन manual credential changes सहित महत्वपूर्ण exceptions दर्ज करता है।

यह अपने-आप remote agent cleanup हल नहीं करता।

पहला, SSH server process वह parent नहीं भी हो सकता जिसकी आपको चिंता है। दूसरा, signal उस process पर लागू होता है जिसने setting लगाई है, अपने-आप सभी descendants पर नहीं। तीसरा, जो remote command fork, double-fork करती है या काम किसी अन्य service को सौंपती है, वह यह संबंध पीछे छोड़ देती है। चौथा, यह Linux-specific है, जो mixed Unix hosts वाली fleet में महत्वपूर्ण है।

इसे केवल वहाँ अतिरिक्त सुरक्षा के रूप में इस्तेमाल करें जहाँ process tree आपका अपना हो। उदाहरण के लिए, छोटा Linux worker एक controlled child को exec करने से पहले `PR_SET_PDEATHSIG` लगा सकता है, जबकि outer wrapper process group और lease का मालिक बना रहे। इससे अलग scope वाले दो failure detectors मिलते हैं। इससे group boundary या remote final record को छोड़ने की अनुमति नहीं मिलती।

यही चेतावनी workload के अंदर `nohup`, `disown` और `setsid` पर लागू होती है। जब कोई व्यक्ति जानबूझकर terminal से आगे काम चलाना चाहता है, तब ये उपयोगी हैं। लेकिन agent run cancel करने पर काम रोकने के वादे के साथ ये असंगत हैं। Launch के समय यह चुनाव स्पष्ट करें।

## दूसरा control channel अक्सर पहले को समाप्त करने से बेहतर है

जब agent चल रही SSH call cancel करता है, तो उसका अपना local execution context पहले ही समाप्त हो रहा हो सकता है। उस खत्म होते process पर अंतिम protocol signal भेजने की जिम्मेदारी डालना races को आमंत्रित करता है। अलग supervisor process को cancellation और reconciliation का मालिक होना चाहिए।

एक काम करने योग्य design इस तरह है:

1. Supervisor cryptographically random run ID बनाता है और remote wrapper invoke करता है।
2. Wrapper run ID वाली directory में अपना PID, PGID, start time और status दर्ज करता है।
3. Supervisor output consume करना शुरू करने से पहले run ID और remote host दर्ज करता है।
4. Cancel पर supervisor fresh control action खोलता है, run record पढ़ता है, expected ownership और age verify करता है और recorded PGID को signal करता है।
5. Supervisor final status record में terminal state आने तक या reporting deadline तक poll करता है।

किसी भी signal से पहले control action को run record validate करना चाहिए। कम-से-कम जाँचें कि record directory expected user की है, PID अभी मौजूद है, recorded PGID `ps` से मेल खाता है और start time शुरू किए गए process के अनुरूप है। Linux PIDs दोबारा इस्तेमाल कर सकता है। Stale PID file और unconditional `kill` से failed cleanup script कई सप्ताह बाद किसी असंबंधित काम को समाप्त कर सकती है।

Cleanup command को «मेरे पिछले task का process kill करो» जैसे generic agent instruction के पीछे न रखें। Agent को opaque run handle दें। Supervisor उस handle को narrowly scoped remote action में बदले। इससे auditing भी साफ़ रहती है: reviewer देखता है कि run `6c4...` ने एक host पर PGID `24182` रद्द करने का अनुरोध किया, न कि agent ने arbitrary kill command बनाई।

Autonomous coding agents के लिए Sallyport, SSH key agent को दिखाए बिना SSH actions चला सकता है। इससे credential custody cancellation contract से अलग रहती है, हालांकि उस contract को फिर भी run handle, group check, remote lease और final record चाहिए।

## सही retry decision side effect पर निर्भर करता है

केवल file पढ़ने वाली command को unknown disconnect के बाद सामान्यतः दोबारा चलाया जा सकता है। User बनाना, migration लागू करना, certificate rotate करना या deployment शुरू करना ऐसा काम नहीं है। SSH layer यह नहीं बता सकती कि operation दोहराने के लिए सुरक्षित हो गया है या नहीं।

Side effects वाली remote commands को run ID से निकला idempotency token दें। Remote program को token को operation के result के साथ store करना चाहिए और वही token फिर मिलने पर मौजूदा result लौटाना चाहिए। यदि यह संभव न हो, तो preflight query जोड़ें जो बताए कि requested change पहले ही हुआ है या नहीं।

Idempotency के बदले cleanup का उपयोग न करें। Perfect `TERM` भी remote API के request स्वीकार करने के बाद लेकिन command के response print करने से पहले पहुँच सकता है। `KILL` भी database transaction commit होने के बाद पहुँच सकता है। Process cleanup यह बताता है कि worker अभी execute कर रहा है या नहीं। यह external effects को rollback नहीं करता।

Control plane में तीन outcomes रखें: completed, confirmed cleanup के साथ cancelled और unknown। Unknown असुविधाजनक है, लेकिन अगले operator को आवश्यक जानकारी देता है: कोई और action जारी करने से पहले remote state देखें। यह wishful thinking पर आधारित हरे Retry button से कहीं बेहतर है।

Cancellation feature तब तैयार है जब आप हर boundary पर इसे interrupt कर सकें और surviving process tree, disk पर मौजूद output, final record और retry decision समझा सकें। यदि disposable host पर ऐसा नहीं कर सकते, तो रात के दो बजे production host पर इस पर भरोसा न करें।
