Stop the GUI and RFC source readers targeting different SAP systems - #7
Merged
Conversation
Two source-read paths returned different text for the same active program Z_EXCEPTION_1. Root cause was NOT a stale or lossy rendition: the two legs were reading two different SAP systems. RPY_PROGRAM_READ is authoritative. Verified live against both systems: the text attributed to the "RFC path" is exactly S4D/100's active source (UNAM=RICO UDAT=20240829); the text attributed to the "SE38 GUI path" is exactly S4H/400's (UNAM=KN789 UDAT=20251019). Both systems host an independently-maintained program of that name. Same-system control, both readers, no alignment fudge: 0 differing lines out of 54, multi-byte comments included. The legs split because each resolves its target through a different chain and neither said which system it used: RFC Connect-SapRfc -> pin -> GUI-active -> default -> sole profile GUI AttachSapSession -> hint -> env -> sole connection -> refuse Two compounding defects put them on different systems: 1. SAPDEV_SESSION_PATH was dead. The check-and-download generator exported it and hardcoded %%SESSION_PATH%% to '', but cscript runs from a later, separate block, so the env var was already gone. Confirmed in repro: the VBS reported "sole connection, sole session" (Strategy 3), never Strategy 2. The AI-session pin was silently discarded. 2. Attach Strategy 3 never checks the pin. "Sole connection" is unambiguous about which session, silent about which system. Pin on S4D/100 + only GUI window on S4H/400 -> it returned S4H/400. A check-fix-upload loop on that basis downloads one system's source and uploads it over another's. Affects /sap-check-abap, /sap-fix-abap, /sap-git serialize and the m365-copilot sap_propose_program_fix tool. Fixes: - sap_attach_lib.vbs: new AssertSapGuiTarget. Every success path emits GUI_TARGET: system=/client=/user=/path=/via= (from GuiSession.Info -- IDs, locale-independent) and refuses hard (exit 2) when SAPDEV_EXPECT_SYSTEM / SAPDEV_EXPECT_CLIENT are set and do not match. Unset = legacy behaviour, still stamped. - sap_connection_lib.ps1: new Set-SapGuiTargetExpectation resolves the SAME profile Connect-SapRfc picks and exports it. Clears rather than guesses when nothing resolves. - sap_rfc_read_source.ps1: result carries System/Client/Host and a source.meta.json provenance sidecar, so a diff can reconcile systems before comparing text. Identity read from RfcDestination.SystemAttributes -- .SystemID is blank on a direct -Server/-Sysnr connection and .Attributes does not exist on RfcDestination. - se38/se37 SKILL.md: generator bakes the resolved session path into %%SESSION_PATH%% (survives the process boundary); execute block calls Set-SapGuiTargetExpectation in the same process as cscript. Also trims the GUI download at the source. AbapEditor.GetLineText is 1-based (index 0 is a phantom empty line) and returns "" out-of-range WITHOUT raising, so `For i = 0 To 9999 ... If Err.Number <> 0 Then Exit For` never terminated early -- 10,001 lines for a 54-line program. The control exposes no line-count property (LineCount/LinesCount/NumberOfLines all error 438). Now reads from 1, truncates at the last non-blank line and emits SOURCE_LINES: <n>. 43,104 -> 3,320 bytes, and file line N == SAP source line N, so syntax-check line numbers index the file directly (the phantom line was silently offsetting them by one). Docs: the se38 caveat blamed this class of difference on the pretty-printer and called RPY_PROGRAM_READ a roadmap item -- corrected, with the verified byte-for-byte agreement recorded and the one genuine editor-vs-storage exception (TYPES in a local class PUBLIC SECTION) kept. Gotcha 4 in parallel_safe_session_attach.md wrongly claimed env vars cross the process boundary; corrected, plus a new gotcha 5 on declaring the expected system. se24's header comment claimed a GetLineText download; it uses the Download menu, so the padding gotcha never applied there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two source-read paths returned different text for the same active program
Z_EXCEPTION_1. The root cause was not a stale or lossy rendition — the two legs were reading two different SAP systems.RPY_PROGRAM_READis authoritative and faithful. Verified live against both systems:data:gv_file_name type string value 'P17E.txt',data:gv_file_name type string value 'D:\SAP Item\P17E.txt',"…STATE=A UNAM=RICO UDAT=20240829STATE=A UNAM=KN789 UDAT=20251019The text attributed to "the RFC path" is exactly S4D/100's active source; the text attributed to "the SE38 GUI path" is exactly S4H/400's. Both systems host an independently-maintained program of that name, diverged since 2024.
Same-system control (both readers, S4H/400, no alignment fudge):
differing_lines=0 / 54, multi-byte (Chinese) inline comments included. There is no general pretty-printer rewrite between storage and display.Why the legs split
Each leg resolves its SAP target through a different chain, and neither stated which system it used:
Connect-SapRfcAttachSapSessionSAPDEV_SESSION_PATH→ sole connection → refuseTwo compounding defects:
SAPDEV_SESSION_PATHwas dead. The check-and-download generator exported it and hardcoded%%SESSION_PATH%%to'', butcscriptruns from a later, separate block — the env var was already gone. Confirmed in repro: the VBS reportedattached ... (sole connection, sole session)(Strategy 3), never Strategy 2. The AI-session pin was silently discarded.Get-SapCurrentSessionPathbehaved correctly here — it returned''; its comment calls that degrade "safe", which holds only when no pin exists.)A check-fix-upload loop on that basis downloads one system's source and uploads it over another's — silently reverting a year of live edits. Affects
/sap-check-abap,/sap-fix-abap,/sap-git serialize, and the m365-copilotsap_propose_program_fixtool.What changed
shared/scripts/sap_attach_lib.vbsAssertSapGuiTarget. Every success path emitsGUI_TARGET: system=… client=… user=… path=… via=…(fromGuiSession.Info— IDs, locale-independent perlanguage_independence_rules.md) and refuses hard (exit 2) whenSAPDEV_EXPECT_SYSTEM/SAPDEV_EXPECT_CLIENTare set and mismatch. Unset = legacy behaviour, still stamped.shared/scripts/sap_connection_lib.ps1Set-SapGuiTargetExpectation— resolves the same profileConnect-SapRfcpicks and exports it. Clears rather than guesses when nothing resolves.shared/scripts/sap_rfc_read_source.ps1System/Client/Host; writes asource.meta.jsonprovenance sidecar next tosource.txt. Header documents the fidelity finding.skills/sap-se38,se37/*_check_and_download.vbsSOURCE_LINES: <n>,WARNon hard cap.skills/sap-se38,se37/SKILL.md%%SESSION_PATH%%; execute block callsSet-SapGuiTargetExpectationin the same process ascscript.skills/sap-se24/..._check_and_download.vbscontributing/parallel_safe_session_attach.md,CLAUDE.mdSecondary fix — download padding and off-by-one
AbapEditor.GetLineTexton SAP GUI 7700 / S/4HANA is 1-based (index 0 is a phantom empty line) and returns""out-of-range without raising, soFor i = 0 To 9999 ... If Err.Number <> 0 Then Exit Fornever terminated early — 10,001 lines for a 54-line program. The control exposes no line-count property (LineCount/LinesCount/NumberOfLines→ error 438;.Textreturns 19 chars). Now:43,104 → 3,320 bytes,SOURCE_LINES: 54, and file line N == SAP source line N (the phantom line was silently offsetting syntax-check line numbers by one).Reviewer notes
RfcDestination.SystemAttributes, not.SystemID(blank on a direct-Server/-Sysnrconnection — would have produced a silent blank stamp) and not.Attributes(does not exist onRfcDestination). Both were dead ends I hit before landing on the working one.AssertSapGuiTargethas no leading underscore on purpose. VBScript identifiers must start with a letter, and because the lib is pulled in viaExecuteGlobal, a_-prefixed Sub fails to compile on the caller'sExecuteGloballine — a confusing failure. Noted inline.SOURCE_LINESmakes any truncation visible rather than silent. A hard cap of 99,999 emits aWARN.temp/testReport/source_read_divergence_rca_20260806.md, which is gitignored (temp/), so it is not in this diff — its substance is reproduced above.Verification
Guard behaviour, live on S4H/400:
Reconcile-then-diff:
node scripts/check-consistency.mjs→OK: 4 plugins, 123 skills, all manifests aligned at version 0.8.1, Tier 3 attach contract clean, screen-baseline coverage 136/136.Deliberately out of scope
SAPDEV_SESSION_PATHpattern (env exported in the generator block,cscriptlaunched later) recurs in ~12 other wrapper blocks across se38/se37 and likely other skills. Each is a latent pin-discarding retarget.$env:SAPDEV_SESSION_PATHwithout invokingcscriptin the same block.🤖 Generated with Claude Code