8 मिनट पढ़ें

SSH stdout और stderr को अलग क्यों रखें?

SSH stdout और stderr को अलग रखने से एजेंट को भरोसेमंद डेटा, उपयोगी निदान, ईमानदार क्रम और साफ निकास परिणाम मिलता है।

SSH stdout और stderr को अलग क्यों रखें?

SSH कमांड के परिणाम में stdout, stderr और समाप्ति को अलग तथ्य के रूप में रखना चाहिए। इन्हें एक स्ट्रिंग में मिला देने पर एजेंट डेटा और निदान में अंतर नहीं कर पाता, ऑपरेटर यह नहीं देख पाता कि कमांड क्यों विफल हुई, और ऑडिट व्यू ऐसा क्रम दिखा सकता है जो कभी था ही नहीं।

इसका हल कोई सुंदर डिलिमिटर नहीं है। दोनों बाइट स्ट्रीम अलग रखें, ट्रांसपोर्ट से उपलब्ध होने पर सीमित प्रेक्षण क्रम दर्ज करें, और निकास स्थिति, निकास सिग्नल, टाइमआउट, रद्द होना तथा ट्रांसपोर्ट विफलता को स्वतंत्र रूप से दिखाएं। कैप्चर सीमा पर थोड़ा अधिक काम करना पड़ता है, लेकिन नीचे की परतों से पार्सर की बहुत सी गलतियां हट जाती हैं।

SSH पहले से दोनों स्ट्रीम अलग रखता है

SSH सामान्य चैनल डेटा और stderr को अलग प्रोटोकॉल संदेशों में ले जाता है। RFC 4254 इन्हें SSH_MSG_CHANNEL_DATA और SSH_MSG_CHANNEL_EXTENDED_DATA कहता है और विस्तारित डेटा प्रकार 1 को SSH_EXTENDED_DATA_STDERR के लिए तय करता है। कोई क्लाइंट लाइब्रेरी अलग रीडर देती है तो वह प्रोटोकॉल में जानबूझकर बचाए गए अंतर को ही सामने रख रही है।

इस अंतर का अर्थ है। प्रोग्राम आम तौर पर मशीन के पढ़ने योग्य परिणाम stdout पर और निदान stderr पर लिखते हैं। कोई कमांड stdout पर मान्य JSON दे सकती है, stderr पर चेतावनी छाप सकती है और फिर भी शून्य लौटा सकती है। दूसरी कमांड कुछ आउटपुट दे सकती है, stderr पर विफलता समझा सकती है और गैर-शून्य लौटा सकती है। केवल बाइट देखकर यह पता नहीं चलता कि कौन सा मामला हुआ।

कैप्चर के समय मिलाने से ऐसी जानकारी मिटती है जिसे बाद का कोई पार्सर वापस नहीं ला सकता। [stderr] जैसा उपसर्ग इंसान की मदद करता है, लेकिन सामग्री बदल देता है। नई पंक्ति वाला विभाजक और खराब है: कोई चंक नई पंक्ति के बिना खत्म हो सकता है, बाइनरी डेटा में कोई भी बाइट आ सकती है, और जोड़ा गया विभाजक दो मान्य टुकड़ों को एक अमान्य दस्तावेज में बदल सकता है।

जब तक उपभोक्ता डिकोडिंग नीति न चुने, हर स्ट्रीम को बाइट मानें। UTF-8 आम है, पर SSH इसकी गारंटी नहीं देता। केवल टेक्स्ट देने वाले औजार भी लोकेल के अंतर, मनमाने बाइट वाले फाइल नाम, या बहु-बाइट अक्षर के बीच कटे लेखन के कारण अमान्य क्रम दे सकते हैं। कच्चे बाइट या दोषरहित एन्कोडिंग रखें, फिर डिकोड किया टेक्स्ट एक व्यू के रूप में दें।

प्रोटोकॉल का यह तथ्य प्रमाण की जिम्मेदारी बदल देता है। अगर परिणाम प्रकार में केवल output: string है, तो वह प्रकार SSH से मिली चीज का गलत वर्णन कर रहा है। सुविधा वाला रेंडरिंग कैप्चर के बाद होना चाहिए, जहां ऑडिट रिकॉर्ड दोबारा लिखे बिना उसे बदला जा सके।

स्ट्रीम की पहचान गंभीरता नहीं है

Stderr का अर्थ फाइल डिस्क्रिप्टर 2 है, विफलता नहीं। हर stderr बाइट को त्रुटि मानने से एजेंट सफल कमांड दोहराते हैं, उपयोगी stdout फेंकते हैं या मामूली चेतावनी के बाद मंजूरी मांगते हैं।

कई परिचित प्रोग्राम प्रगति, विस्तृत ट्रेस, प्रॉम्प्ट और चेतावनियां stderr पर लिखते हैं। कोई कंपाइलर stdout को बने हुए आउटपुट के लिए बचाकर प्रगति कहीं और बता सकता है। कोई कमांड बिना कुछ लिखे गैर-शून्य स्थिति के साथ विफल भी हो सकती है। यह संबंध उपयोगी प्रमाण है, कोई बूलियन नियम नहीं।

कम से कम ये चार बातें अलग रखें:

  • stdout और stderr बताते हैं कि बाइट कहां से आए।
  • exit_status या exit_signal बताता है कि दूर का प्रोग्राम कैसे समाप्त हुआ।
  • transport_error बताता है कि SSH कार्रवाई खुद पूरी हुई या नहीं।
  • timed_out और cancelled स्थानीय हस्तक्षेप बताते हैं।

इससे पार्सर की आम गलती रुकती है जिसमें stderr != empty को success = false बना दिया जाता है। सफलता का सामान्य अर्थ होना चाहिए कि कमांड शुरू हुई, चैनल पूरा हुआ और दूर की स्थिति शून्य थी। आपका अनुप्रयोग किसी खास कमांड पर अधिक सख्त नियम लगा सकता है, लेकिन वह नियम कमांड एडाप्टर में होना चाहिए, सामान्य SSH रनर में नहीं।

उलटी गलती भी उतनी ही नुकसानदेह है। कुछ रैपर सफलता पर केवल stdout लौटाते हैं और विफलता पर पूरे परिणाम को अपवाद से बदल देते हैं। अपवाद में stderr का कटा हुआ अंतिम हिस्सा हो सकता है, जबकि आंशिक stdout गायब हो जाता है। एजेंट को जब सबसे अधिक प्रमाण चाहिए, तभी उसे कम जानकारी मिलती है।

दूर के निदान, कनेक्शन विफलता, टाइमआउट और पार्सर विफलता को एक ही error फील्ड में न भरें। इनके दोबारा कोशिश करने के नियम अलग हैं। DNS विफलता पर दोबारा कोशिश ठीक हो सकती है। उपयोग की गलती वाला निकास 2 आम तौर पर दोबारा चलाने योग्य नहीं है। stdout पर अमान्य JSON मिले तो मूल बाइट बचने चाहिए, ताकि डेवलपर तय कर सके कि कमांड गलत थी या पार्सर।

परिणाम अनुबंध व्याख्या से पहले तथ्य बचाए

लंबे समय तक काम आने वाला परिणाम ऑब्जेक्ट मूल प्रमाण रखता है और अज्ञात अवस्थाओं को साफ दिखाता है। उसे हर कॉलर से फॉर्मेट किए ट्रांसक्रिप्ट का उलटा अर्थ निकलवाना नहीं चाहिए।

यह अनुबंध जानबूझकर साधारण है:

{
  "stdout": {"encoding": "base64", "data": "Li4u", "truncated": false},
  "stderr": {"encoding": "base64", "data": "Li4u", "truncated": false},
  "events": [
    {"seq": 1, "stream": "stdout", "offset": 0, "length": 48},
    {"seq": 2, "stream": "stderr", "offset": 0, "length": 19}
  ],
  "termination": {
    "kind": "exit",
    "exit_status": 0,
    "exit_signal": null,
    "core_dumped": null
  },
  "transport_error": null,
  "started_at": "2026-07-24T10:20:30.123Z",
  "finished_at": "2026-07-24T10:20:31.456Z"
}

दोनों स्ट्रीम ऑब्जेक्ट अधिकृत सामग्री हैं। हर इवेंट टेक्स्ट दोबारा रखने के बजाय बाइट की सीमा का संदर्भ देता है, इसलिए व्यूअर पेलोड की नकल किए बिना ट्रांसक्रिप्ट बना सकता है। seq केवल कैप्चर का प्रेक्षण क्रम है। वह यह दावा नहीं करता कि दूर की लिखाइयां ठीक इसी क्रम में हुईं।

termination.kind में कम से कम exit, signal, timeout, cancelled, transport_error और unknown होने चाहिए। जादुई कोड की जगह null हो सकने वाले फील्ड रखें। SSH निकास स्थिति का न होना शून्य नहीं है, और स्थानीय टाइमआउट निकास 124 नहीं है, जब तक दूर के होस्ट पर shell या timeout औजार ने सच में 124 न बनाया हो।

हर स्ट्रीम का truncation अलग रखें। एक वैश्विक truncated ध्वज पार्सर को नहीं बता सकता कि stdout पर पूरा JSON बचा है या केवल लंबे stderr का अंत खोया है। पता हो तो कैप्चर और छोड़े गए बाइट की गिनती लिखें। अगर केवल आरंभ और अंत रखते हैं, तो उन्हें अलग खंड के रूप में दिखाएं, ऐसे न जोड़ें जैसे बीच का हिस्सा कभी था ही नहीं।

टाइमस्टैम्प देरी और जांच में मदद करते हैं, लेकिन चंक का क्रम तय करने के लिए दीवार घड़ी का समय न इस्तेमाल करें। घड़ी छलांग लगा सकती है और दो साथ चल रहे रीडर चुनी गई सूक्ष्मता पर एक ही टाइमस्टैम्प पा सकते हैं। किसी एक क्रमबद्ध बिंदु पर अनुक्रम संख्या दें। रनटाइम उपलब्ध कराए तो monotonic अवधि अलग रखें।

क्लाइंट के निर्भर होने से पहले अनुबंध का संस्करण तय करें। फील्ड जोड़ना आम तौर पर सुरक्षित है, लेकिन events.seq को आगमन क्रम से प्रदर्शन क्रम बनाना अर्थ का टूटना है, भले JSON का आकार वही रहे।

अलग स्ट्रीम के साझा क्रम की सीमा तय है

आप अपने SSH स्टैक द्वारा देखे गए चैनल संदेशों का क्रम रख सकते हैं, लेकिन आम तौर पर stdout और stderr पर दूर के प्रोग्राम की लिखाइयों का क्रम प्रमाणित नहीं कर सकते। यह सीमा डेटा मॉडल और इंटरफेस की भाषा में दिखनी चाहिए।

एक स्ट्रीम में बाइट क्रम में रहते हैं। दो स्ट्रीम के बीच कई परतों पर बफरिंग होती है: दूर का भाषा रनटाइम, libc, पाइप, SSH सर्वर, ट्रांसपोर्ट पैकेट, क्लाइंट लाइब्रेरी और आपके अपने रीडर टास्क। टर्मिनल से न जुड़े stdout पर ब्लॉक बफरिंग हो सकती है, जबकि stderr जल्दी फ्लश हो सकता है। इसलिए बाद की stderr लिखाई, पहले की stdout लिखाई से पहले दिखाई दे सकती है।

RFC 4254 SSH कार्यान्वयन द्वारा भेजे गए चैनल संदेशों का क्रम रखता है। यह उपयोगी है, और इन संदेशों को दिखाने वाला लाइब्रेरी कॉलबैक सही प्राप्ति क्रम दे सकता है। लाइब्रेरी जैसे ही डेटा को स्वतंत्र stdout और stderr रीडर में बांटती है, दो goroutine या असिंक्रोनस कॉलबैक readiness बताने के लिए दौड़ते हैं। शेड्यूलर उन्हें जिस क्रम में चलाता है, वह स्थानीय डिलीवरी का प्रेक्षण है, दूर के सोर्स कोड क्रम की पुनर्रचना नहीं।

यह छोटा कमांड दिखाता है कि टेस्ट को एक सार्वभौमिक मिला हुआ ट्रांसक्रिप्ट क्यों नहीं मांगना चाहिए:

sh -c 'printf "out-1\n"; printf "err-1\n" >&2; printf "out-2\n"; printf "err-2\n" >&2'

टर्मिनल अक्सर सोर्स जैसा क्रम दिखाता है। दोनों डिस्क्रिप्टर को >all.log 2>&1 से एक फाइल में भेजने पर shell उन्हें एक ही गंतव्य की ओर करता है, जिससे उस प्रक्रिया के लिए कर्नेल से चलने वाला एक लेखन मार्ग मिलता है। अलग पाइप से कैप्चर करने पर प्रेक्षक चंक किसी और क्रम में पा सकता है। बफरिंग वाला भाषा रनटाइम जोड़ने पर अंतर और बढ़ता है।

अगर अलग स्ट्रीम का बिल्कुल सही साझा कालक्रम जरूरी है, तो निर्माता का अनुबंध बदलें। दूर का प्रोग्राम अपने अनुक्रम नंबर वाले संरचित रिकॉर्ड एक स्ट्रीम पर लिखे, या SSH के देखने से पहले दोनों डिस्क्रिप्टर को एक दूर के सिंक पर भेजें। इससे निर्माता के पास स्वतंत्र स्ट्रीम छोड़कर परिभाषित क्रम मिलता है। सामान्य SSH क्लाइंट बाद में खोया तथ्य बना नहीं सकता।

ऑडिट टेक्स्ट में observed sequence लिखें, execution sequence नहीं। यह कानूनी भाषा वाली सजावट नहीं है। इससे जांचकर्ता शेड्यूलर के समय को कारण और परिणाम समझने की गलती नहीं करता।

चंक की सीमाएं ट्रांसपोर्ट की देन हैं

Agent process को एक बार मंजूरी दें
Session authorization पहली SSH call से पहले process की code-signing authority दिखाता है।

एक रीड कॉलबैक कोई पंक्ति, रिकॉर्ड या दूर की एक write कॉल नहीं है। ऐसा मानने वाले पार्सर टेस्ट में चलते हैं और भार बढ़ने पर टूटते हैं।

एक कॉल कई चंक में आ सकती है। कई लिखाइयां एक चंक में आ सकती हैं। UTF-8 कोड पॉइंट, ANSI escape sequence या JSON टोकन सीमा के बीच कट सकता है। वही कमांड अगली बार समान आउटपुट के साथ भी अलग चंक दे सकती है।

कैप्चर परत को बाइट append कार्रवाई के आसपास बनाएं। हर स्ट्रीम के लिए चंक को बफर या spool फाइल में जोड़ें और उसका offset तथा length लिखें। लाइब्रेरी संदेश क्रम से दे तो वहीं seq दें। स्वतंत्र रीडर दे तो चंक की सूचना एक collector को भेजें और लिखें कि क्रम collector की प्राप्ति दिखाता है।

पंक्तियों में बांटना derived view का काम है। हर स्ट्रीम के लिए एक incremental decoder और एक अधूरी पंक्ति का बफर रखें। stdout और stderr का पंक्ति बफर साझा न करें, क्योंकि बिना अंत वाला stdout टुकड़ा और उसके बाद stderr की पंक्ति मिलकर नकली पंक्ति नहीं बननी चाहिए। स्ट्रीम बंद होने पर आखिरी अधूरी पंक्ति दिखाएं, चुपचाप न छोड़ें।

JSON को आम तौर पर stdout का अंत आने और कमांड समाप्ति पता चलने के बाद पार्स करें। स्ट्रीमिंग JSON प्रोटोकॉल अलग है: उसे newline-delimited JSON, length prefix या लिखित incremental grammar जैसी साफ framing चाहिए। चंक देखकर रिकॉर्ड सीमा का अनुमान लगाना स्ट्रीमिंग नहीं, एक race है।

बाइनरी आउटपुट का रास्ता भी स्पष्ट हो। JSON में base64 सरल और पोर्टेबल है, हालांकि आकार बढ़ाता है। बड़े परिणाम के लिए blob reference चल सकता है, यदि ऑडिट सिस्टम retention और integrity की गारंटी दे। replacement character लगाकर मूल सामग्री न फेंकें। ऐसा करने से यह छिप जाता है कि खराबी दूर के औजार, ट्रांसपोर्ट एडाप्टर या व्यूअर में आई।

सीमा पढ़ते समय लागू होनी चाहिए, सब कुछ मेमोरी में भरने के बाद नहीं। एक स्ट्रीम retention सीमा पार कर दे, तब भी दोनों को drain करते रहें, वरना भरी पाइप पर दूर की प्रक्रिया रुक सकती है। अनुमत आरंभ, अंत या बाहरी spool रखें, छोड़े गए बाइट गिनें और बंद होने या रद्द होने तक पढ़ते रहें।

छद्म टर्मिनल संरचना के बदले टर्मिनल व्यवहार देता है

जिस कमांड का stdout पार्स होना है, उसके लिए छद्म टर्मिनल न मांगें। PTY इंसानी सत्र में उपयोगी है, लेकिन प्रोग्राम का वातावरण बदलता है और अक्सर SSH क्लाइंट द्वारा पहचान बचाने से पहले stdout तथा stderr को एक ही टर्मिनल डिवाइस पर भेज देता है।

प्रोग्राम जांचते हैं कि डिस्क्रिप्टर टर्मिनल है या नहीं। वे रंग चालू कर सकते हैं, carriage return से प्रगति बना सकते हैं, बताई गई चौड़ाई पर पंक्ति मोड़ सकते हैं, इनपुट मांग सकते हैं या block buffering से line buffering पर जा सकते हैं। PTY से कैप्चर किए बाइट इसलिए उसी कमांड के बिना PTY वाले बाइट से अलग हो सकते हैं। यह दिखाई देने वाला व्यवहार है, केवल प्रदर्शन विकल्प नहीं।

OpenSSH क्लाइंट के विकल्प इस अंतर को दिखाते हैं: -T छद्म टर्मिनल allocation बंद करता है, -t उसे मांगता है और दोहराया -t उसे मजबूर कर सकता है। ऑटोमेशन में डिफॉल्ट रूप से PTY न लें। केवल तब लें जब दूर के प्रोग्राम को टर्मिनल व्यवहार चाहिए और परिणाम अनुबंध साफ कहता हो कि स्ट्रीम का अलगाव उपलब्ध नहीं है।

PTY क्रम को अधिक सच्चा नहीं बनाता। वह एक टर्मिनल बाइट स्ट्रीम दे सकता है, इसलिए उस सीमा पर प्रदर्शन क्रम तय होता है, लेकिन टर्मिनल मिलने के कारण प्रोग्राम और लाइब्रेरी अलग बफरिंग कर सकते हैं। आपने स्वतंत्र प्रमाण देकर interactive व्यवहार लिया है, बिना PTY वाली प्रक्रिया का कालक्रम नहीं खोजा।

यह अंतर जिद्दी गलतियों का कारण समझाता है। डेवलपर shell में कमांड चलाता है और साफ, रंगीन, उचित क्रम वाली प्रगति देखता है। एजेंट वही टेक्स्ट बिना PTY चलाता है, stdout block buffered हो जाता है, stderr पहले आता है और पार्सर को control code रहित मशीन आउटपुट बाद में मिलता है। फिर कोई ट्रांसक्रिप्ट को हाथ से किए टेस्ट जैसा बनाने के लिए PTY मजबूर करता है और रंग कोड या प्रॉम्प्ट स्ट्रीम में आकर JSON पार्सिंग तोड़ते हैं।

API में interactive और structured execution को अलग mode मानें। Structured mode अलग स्ट्रीम और टर्मिनल emulation के बिना स्थिर capture behavior का वादा करे। Interactive mode टर्मिनल transcript, उसके dimensions और साफ संकेत लौटाए कि मूल stdout तथा stderr की पहचान नहीं रखी गई। response एक जैसा दिखे तो request options में छिपा pty: true पर्याप्त नहीं है।

Remote startup files एक और समस्या लाती हैं। RFC 4254 चेतावनी देता है कि subsystem शुरू करते समय shell initialization अनचाहा output दे सकता है और protocol को उसे पहचानने के लिए खास marker रखने की सलाह देता है। यही सीख command adapter पर लागू होती है: अपने नियंत्रण वाला सबसे सीधा executable path चलाएं, बेकार interactive shell से बचें और अनपेक्षित शुरुआती bytes को प्रमाण मानें, banner जैसी दिखने वाली सामग्री चुपचाप न हटाएं।

अगर command को सच में password prompt या terminal control चाहिए, तो उसके transcript को parser-ready न बताएं। agent को सीमित inputs वाला विशेष interaction tool और terminal semantics के लिए बना transcript दें। यह रास्ता अलग रहने पर सामान्य SSH actions faithful stdout, faithful stderr और termination outcome दे सकते हैं।

निकास स्थिति परिणाम है, अपवाद स्ट्रिंग नहीं

हर remote action lock करें
Hardware-gated vault lock होने पर Sallyport हर SSH action रोक देता है।

RFC 4254 एक exit-status चैनल अनुरोध और अलग exit-signal रूप परिभाषित करता है। वह स्थिति लौटाने की सलाह देता है, लेकिन क्लाइंट को उसे अनदेखा करने की अनुमति भी देता है। इसलिए API में स्पष्ट अज्ञात परिणाम होना चाहिए, स्थिति न आने पर सफलता नहीं माननी चाहिए।

शून्य स्थिति आम तौर पर सफलता बताती है, निश्चितता नहीं। RFC 4254 जानबूझकर ऐसा सीमित शब्द इस्तेमाल करता है, क्योंकि कमांड के नियम ट्रांसपोर्ट से ऊपर हैं। फिर भी सामान्य स्तर पर स्थिति मुख्य संकेत है। होस्ट भाषा की process convention में बदलने से पहले protocol का unsigned value बचाएं।

Signal termination कोई negative exit status नहीं है। signal name, मिलने पर core-dump flag और remote explanation अलग रखें। consumer display के लिए 128 plus signal value जैसा shell number चाहे तो बना सकता है। audit record में SSH के facts रहने चाहिए।

Code और UI में इन outcomes को अलग दिखाएं:

  • Remote command ने status लौटाया।
  • Remote side ने signal termination बताया।
  • Channel किसी भी report के बिना बंद हुआ।
  • Command start confirm होने से पहले client विफल हुआ।
  • Partial output आने के बाद connection विफल हुआ।

चौथी स्थिति को remote exit 255 न बनाएं, केवल इसलिए कि OpenSSH command-line client अपने errors के लिए अक्सर 255 इस्तेमाल करता है। library transport error का अलग type होता है। अगर ssh executable को subprocess के रूप में चलाते हैं, wrapper केवल 255 जान सकता है, इसलिए उसका local stderr रखें और boundary को ईमानदारी से label करें।

Completion का अर्थ सारा output drain होना भी है। Go का os/exec documentation चेतावनी देता है कि StdoutPipe या StderrPipe से पूरी read होने से पहले Wait बुलाना गलत है। Node.js भी वैसी सीमा दिखाता है: उसका exit event आने पर stdio खुला रह सकता है, जबकि close stream closure के बाद आता है। ये manual local subprocess बताते हैं, लेकिन design lesson SSH helper पर भी लागू है। final result तभी publish करें जब termination पता हो और दोनों output reader अपनी terminal state में पहुंच चुके हों।

Timeout और cancellation के अलग fields हों। system जानता हो तो लिखें कि cancellation किसने शुरू किया, signal मांगा गया या नहीं और channel सच में बंद हुआ या नहीं। timed_out: true लिखकर बाद में मिले remote exit report को न फेंकें; investigation में दोनों events जरूरी हो सकते हैं।

पार्सर stdout पढ़े और बाकी सब संभालकर रखे

किसी खास कमांड के पार्सर को stdout बाइट, समाप्ति और सामग्री metadata मिलना चाहिए। उसे मिला हुआ transcript देकर diagnostic पंक्तियां पहचानने को नहीं कहना चाहिए।

मान लें agent remote inventory command चलाता है जो stdout पर JSON देने का वादा करती है। adapter पहले जांचे कि SSH action का ज्ञात termination मिला, फिर command की status policy लगाए, फिर stdout decode और parse करे। stderr supporting evidence के रूप में जुड़ा रहे। warning JSON decoder में नहीं जाएगी और parse error warning को नहीं मिटाएगी।

Parser failure को command result के साथ लौटाएं, उसकी जगह नहीं। उपयोगी error कह सकता है कि stdout byte 418 अमान्य था और साथ में original stdout, stderr, exit status तथा truncation flags रख सकता है। इस bundle से agent invocation सुधारने, stable locale के साथ retry करने या सही evidence किसी व्यक्ति को देने का फैसला कर सकता है।

Structured agent path पर CombinedOutput नाम की convenience API से बचें। Go manual इसका काम साफ बताता है: यह standard output और standard error को मिलाकर लौटाता है। एक बार की diagnostic command में यह ठीक है, reusable result contract में गलत है, क्योंकि खोए labels बाद में निकाले नहीं जा सकते।

Text commands के लिए भी command-specific choices चाहिए। कोई parser stdout को newline-delimited records मान सकता है और stderr को साधारण diagnostic text दिखा सकता है। कोई दूसरा zero status और empty stdout को valid empty result मान सकता है। इन rules को command definition और tests के पास रखें, transport में न दबाएं।

Prompt बनाते समय structured fields इस्तेमाल करें। model को exit status: 2 बताएं, stdout और stderr अलग labeled blocks में दें और content truncated हो तो कहें। untrusted remote output को बिना boundary instructions में न जोड़ें। output में prompt जैसा text आ सकता है, इसलिए उसे data मानें और container format के लिए escape करें।

Agent को prose से success तय नहीं करनी चाहिए। उसे termination.kind और exit_status जैसे machine fields दें, फिर prose समझाए। इससे token कम लगते हैं और error शब्द वाली warning successful status को override नहीं करती।

ऑडिट व्यू को दो ईमानदार रूप चाहिए

Runs और calls अलग रखें
Sessions agent run track करता है, Activity Sallyport से मांगी हर command दर्ज करता है।

ऑडिट रिकॉर्ड और इंसान के लिए ट्रांसक्रिप्ट के काम अलग हैं। रिकॉर्ड बाइट और metadata बचाता है; ट्रांसक्रिप्ट उन्हें पढ़ने में मदद करता है।

उपयोगी call view ऊपर status strip दिखाता है: command, host identity, start और finish time, termination kind, exit status या signal, byte counts तथा truncation। नीचे authoritative अलग stdout और stderr tabs दें। combined tab events को observed sequence से interleave कर सकता है, लेकिन हर row पर स्थायी stream label हो।

Stream identity केवल color से न दिखाएं। text label और हर original stream के लिए copy action दें। combined view copy करने पर labels शामिल हों या warning आए कि यह rendering है, क्योंकि provenance के बिना pasted output फिर वही शुरुआती समस्या बनाता है।

लंबी पंक्तियों, carriage returns और terminal control codes को सावधानी से render करें। default में control characters escape करें। बार बार \r लिखने वाला progress bar पुरानी audit content को terminal की तरह overwrite न करे। terminal emulation केवल optional derived view हो और raw representation उपलब्ध रहे।

Search result के साथ stream, byte offset और event sequence लौटाएं। stderr filter करने से sequence numbers न बदलें। content truncated हो तो missing bytes की जगह साफ gap marker और recorded count दिखाएं। prefix और suffix को ऐसे न जोड़ें जैसे source में साथ थे।

Timeline termination को last observed chunk के बाद तभी रखे जब capture पुष्टि करे कि finalization से पहले दोनों readers बंद हुए। connection टूटे तो last output event, transport failure और unknown remote outcome दिखाएं। इसे लाल failed badge में बदलना program failure और evidence loss का अंतर मिटा देता है।

Sallyport अपने stateless sp-ssh helper से SSH actions चलाता है और हर call को Activity journal में लिखता है, इसलिए separation helper result boundary पर होनी चाहिए, agent या audit view की formatting से पहले। यहां काम की product behavior कोई clever transcript नहीं, बल्कि इतना evidence रखना है कि agent और इंसान अपने निष्कर्ष निकाल सकें।

सफल ट्रांसक्रिप्ट नहीं, विफलता के रूप जांचें

Parser test suite chunking, stream timing, termination, encoding और retention limits को स्वतंत्र रूप से बदले। एक merged string का snapshot मुख्य रूप से formatter को test करता है।

पूरे नियंत्रण वाले protocol-level events देने वाले fake channel source से शुरू करें। एक stdout payload को हर संभव split point पर बांटकर दें। फिर multi-byte UTF-8 sample, ANSI sequence और बिना \n वाली अंतिम पंक्ति के साथ ऐसा करें। हर split पर stored bytes समान रहने चाहिए।

Known sequence numbers के साथ stdout और stderr events interleave करें और जांचें कि अलग buffers, range offsets तथा combined view सहमत हैं। independent reader implementation में scheduling delay डालें और केवल per-stream byte order तथा collector observation order assert करें। producer source-code order पर जोर देने वाला test ऐसी guarantee मांगता है जो system के पास नहीं है।

सामान्य fixtures से छूटने वाले termination combinations शामिल करें: stderr के साथ zero, empty stderr के साथ nonzero, partial stdout के साथ signal, status के बिना channel close, दोनों stream में data के बाद transport failure, timeout के बाद late close और start confirmation से पहले cancellation। हर एक अलग structured outcome दे।

एक समय में एक stream पर छोटा limit लगाएं। जांचें कि stdout truncation stderr को truncated न बताए, discarded byte counts सही हों, readers drain करते रहें और final status फिर भी आए। फिर दोनों stream एक साथ भरें। इससे classic deadlock पकड़ा जाता है जिसमें code पहले पूरा stdout पढ़ता है और उसके बाद stderr शुरू करता है।

Byte invariants के लिए property tests अच्छे हैं। arbitrary byte slices और chunk boundaries बनाएं, collector से गुजारें और मांगें कि retained event ranges को जोड़ने पर retained stream content मिले। event schedules और stream contents अलग generate करें ताकि test chunking को meaning न समझे।

अंत में हर export जांचें। JSON null और zero का फर्क रखे। text transcript stream label करे। redaction stored offsets को mapping के बिना न बदले, या अलग derived artifact बनाए। audit format तब भरोसे योग्य है जब वह कठिन failures को साफ दिखाए, उन्हें साफ लेकिन झूठी कहानी में न बदल दे।

कच्ची स्ट्रीम रखें, देखे गए क्रम को label करें और परिणाम प्रकाशित करने से पहले दोनों का drain और termination पूरा होने दें। एक बार flattened string agent message या audit log में पहुंच गई तो खोए अंतर वापस नहीं आते और हर अगली परत केवल अनुमान लगा सकती है।

सामान्य प्रश्न

क्या stderr आने पर SSH कमांड विफल माननी चाहिए?

नहीं। Stderr फाइल डिस्क्रिप्टर 2 पर लिखे बाइट बताता है, कमांड का परिणाम नहीं। सामान्य परिणाम के लिए SSH निकास स्थिति या सिग्नल इस्तेमाल करें, फिर किसी खास कमांड का एडाप्टर तय करे कि निदान स्वीकार्यता बदलता है या नहीं।

क्या SSH stdout और stderr का बिल्कुल सही क्रम रख सकता है?

SSH क्लाइंट द्वारा देखे गए चैनल संदेशों का क्रम रख सकता है, लेकिन यह दूर के प्रोग्राम के लेखन क्रम का प्रमाण नहीं है। बफरिंग और स्वतंत्र रीडर बाइट दिखने का समय बदल सकते हैं, इसलिए मिले हुए ट्रांसक्रिप्ट को प्रेक्षण क्रम कहें।

stderr खाली न हो तो stdout को JSON मानकर पार्स करना सुरक्षित है?

हां, अगर कमांड अनुबंध stdout में JSON कहता है और समाप्ति उस अनुबंध को पूरा करती है। केवल stdout पार्स करें और stderr को निदान के रूप में रखें; मिला हुआ ट्रांसक्रिप्ट JSON पार्सर को कभी न दें।

SSH कमांड निकास स्थिति न लौटाए तो क्या करें?

शून्य मानने के बजाय परिणाम को अज्ञात दिखाएं। दोनों स्ट्रीम और ट्रांसपोर्ट त्रुटि रखें, क्योंकि बिना स्थिति का बंद चैनल सफलता या कमांड विफलता में से किसी को सिद्ध नहीं करता।

एजेंट को कच्चे बाइट मिलने चाहिए या डिकोड किया टेक्स्ट?

स्थायी परिणाम में बाइट या दोषरहित एन्कोडिंग रहनी चाहिए। सुविधा के लिए डिकोड टेक्स्ट भी दे सकते हैं, लेकिन डिकोडिंग त्रुटि दर्ज करें और स्रोत बचाए बिना अमान्य बाइट न बदलें।

हर SSH कमांड के लिए छद्म टर्मिनल क्यों न लें?

छद्म टर्मिनल बफरिंग बदलता है और अक्सर structured parser के लिए जरूरी stdout तथा stderr का अंतर मिटाता है। सच में interactive command के लिए लें, मशीन से पढ़े जाने वाले output वाली automation के लिए नहीं।

बड़ा SSH output कैसे काटें?

stdout और stderr पर अलग सीमा लगाएं, रखे तथा छोड़े गए बाइट गिनें और दोनों स्ट्रीम drain करते रहें। हर missing span साफ दिखे, ताकि आरंभ और अंत पास-पास समझे न जाएं।

SSH परिणाम कब पूरा होता है?

जब termination state पता हो या साफ तौर पर unknown हो और दोनों stream reader समाप्त हों। केवल process exit काफी नहीं, क्योंकि buffered stdout या stderr अभी आ सकता है।

मिले हुए SSH transcript में chunks कैसे label करें?

हर rendered range पर दिखाई देने वाला stdout या stderr label और observed sequence रखें। अलग stream views authoritative हों और copied combined text में labels रहें, ताकि paste के बाद provenance न खोए।

stdout और stderr handling test करने का अच्छा तरीका क्या है?

मनमाने बाइट बनाएं, chunk boundaries और scheduling बदलें और हर stream का exact reconstruction जांचें। signals, missing status, partial output, timeout, transport failure और अलग truncation limits भी शामिल करें।

Sallyport

Sallyport आपके AI एजेंट के लिए API कॉल और SSH कमांड चलाता है। कुंजियाँ आपके Mac पर एक लोकल वॉल्ट में रहती हैं; आप हर रन को स्वीकृत करते हैं और हर क्रिया एक सीलबंद जर्नल में दर्ज होती है।

© 2026 Sallyport · Apache-2.0 के तहत ओपन सोर्स · Oleg Sotnikov