Install
$ agentstack add skill-ilia-inovaflow-s4hana-create-record-skills-s4hana-update-record ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
s4hana-update-record
Generic update skill for SAP S/4HANA records. Verified pattern against .s4hana.cloud.sap 2026-05-12 — 6 different entity types successfully PATCH'd; 2 confirmed entity-disabled; 1 "silent success" trap documented.
When to trigger
Verbs: update / change / edit / modify / patch / set / rename / adjust / correct / backdate / amend Objects: any existing S/4HANA record — PO, supplier, customer, BP, PIR, SES, product description, etc.
When NOT to trigger
- User wants to CREATE a record → use
s4hana-create-*skills - User wants to DELETE → Cloud Public blocks DELETE on most entities. Use block-and-rename (PATCH
BusinessPartnerIsBlocked=trueetc.) instead. - User wants to update Supplier Invoice or Material Document → those are entity-disabled (405). They need cancel-and-recreate (use the cancellation flow in the relevant create skill instead).
Hard rules (never violate)
- GET before PATCH, GET after PATCH. Always show user the BEFORE state, then PATCH, then VERIFY the change actually took effect with a fresh GET. Many fields return 204 "success" but don't actually update — only the post-PATCH GET reveals the truth.
- Confirm with user before PATCHing batches >3. Show the planned BEFORE/AFTER diff for the first record, get explicit confirmation, then proceed.
- If post-PATCH GET shows unchanged value, surface it as a "silent failure" — DON'T claim success.
- Never invent values. If the user says "set PO X date to Aug 2025", use a specific date they confirmed, not a random one.
- Scripts go in
mcp-server/src/sap/.tmp/-/update-/. Never commit, never modify.env.
Phase 0 — Setup check (MANDATORY, no exceptions)
Before any API call, before any tool use, do this check. Even if you already know credentials from earlier in the conversation — IGNORE that knowledge and re-check from scratch each invocation.
- Announce: tell the user "Checking for SAP credentials in ``..."
- Check ONLY these two sources:
- A
.envfile at./.env(in the current working directory — NOT parent dirs, NOT~/.env, NOT any memory file) - Shell-exported env vars:
SAP_HOSTANDSAP_AUTH_MODEboth present
- If neither: auto-create
./.envfrom the bundled template (curl fromhttps://raw.githubusercontent.com/ilia-inovaflow/s4hana-create-record-skills/main/.env.example), append.envto.gitignoreif not already there, tell the user clearly what to fill in (auth mode + host + creds), and wait for them to say "ready" before making any API call. Never overwrite an existing.env. - If credentials are present: report back to the user: "✓ Loaded credentials for `
in` mode. Proceeding with [task]."
Never use credentials from conversation memory, from another project's .env, or from any tenant the user previously worked with in a different session. Each project gets its own .env. See [shared/setup-check.md](../../shared/setup-check.md) for the full rationale and edge cases.
Phases
Phase 1 — Identify entity + record + field(s)
Parse the user's intent:
- Entity: PO, BP, PIR, etc. — map to OData service + entity set.
- Record key(s): Single-key (e.g.
PurchaseOrder='4500000020') or composite (e.g. PIR org-plant has 4 keys). - Fields: What does the user want changed? Translate domain language ("payment terms") to API field names (
PaymentTerms).
If ambiguous, ask once before proceeding. Show the user your planned interpretation: "I'm going to update PO 4500000020's PurchaseOrderDate to 2025-08-15. Correct?"
For known entities, use the table below to find the right endpoint. For unknown entities, switch to research mode (Phase 1b).
Phase 1b — Research mode (for unknown entities)
If the entity isn't in the verified table:
- Identify the OData service via
$metadatalookup or search SAP API Hub. - Fetch the entity's
$metadataand inspect:
- Key fields: which fields make up the primary key
sap:updatable="false"on properties (write-once fields)sap:creatableannotations (which fields appear in PATCH vs POST)
- Try minimal PATCH, iterate on errors.
- Document the working pattern in
references/mutable-fields-catalog.mdfor next time.
Phase 2 — GET current state (before)
GET /sap/opu/odata/sap//?$select=&$format=json
Authorization: Basic
Accept: application/json
Show user: "Current value of PurchaseOrderDate is 2026-04-30. Want me to change it to 2025-08-15?"
For batch operations, GET each record (or a sample) and show the user a table of planned changes before any PATCH.
Phase 3 — PATCH
PATCH /sap/opu/odata/sap//?sap-client=
Authorization: Basic
X-CSRF-Token:
Cookie:
Content-Type: application/json
Accept: application/json
{"": ""}
PATCH semantics: partial update. Only send the field(s) you want to change — don't echo the full record. Body should be minimal.
Expected response: HTTP 204 No Content (success) or HTTP 400 with error.innererror.errordetails for failures.
Phase 4 — GET to verify (after)
GET /sap/opu/odata/sap//?$select=&$format=json
Compare BEFORE → AFTER for each field. Report to user:
- ✅ Changed as expected:
PurchaseOrderDate: 2026-04-30 → 2025-08-15 - ❌ Unchanged despite 204:
NetPriceAmount: 50.00 → 50.00 (silent failure — field may be read-only despite 204 response) - ❌ Rejected: log the error details
Phase 5 — Report
After single or batch: summarize changes, surface any silent failures or rejections, write log to disk.
Output structure
mcp-server/src/sap/.tmp/-/update-/
├── targets.json # records to update + proposed changes
├── before.json # GET-before snapshots
├── update-log.jsonl # one line per PATCH attempt
├── after.json # GET-after snapshots
├── diff.md # human-readable before/after table
└── results.json
Verified update patterns (mutable-fields catalog)
See references/mutable-fields-catalog.md for the full per-entity catalog. Quick summary:
| Entity | Service | What works | Notes | |---|---|---|---| | A_BusinessPartner | API_BUSINESS_PARTNER | OrganizationBPName1 and most header fields | Single key: BusinessPartner | | A_BusinessPartnerAddress | API_BUSINESS_PARTNER | CityName, StreetName, PostalCode, Country | Composite key: BusinessPartner + AddressID | | A_ProductDescription | API_PRODUCT_SRV | ProductDescription | Composite key: Product + Language. Useful for Toblerone-style overrides | | A_PurchaseOrder | API_PURCHASEORDER_PROCESS_SRV | PurchaseOrderDate, PaymentTerms, header text, most header fields | NOT Supplier, CompanyCode, PurchasingOrganization — write-once | | A_PurchaseOrderItem | API_PURCHASEORDER_PROCESS_SRV | PurchaseOrderItemText, NetPriceAmount, OrderQuantity, Plant, StorageLocation | NOT Material, PurchaseOrderItemCategory — write-once | | A_ServiceEntrySheet | API_SERVICE_ENTRY_SHEET_SRV | ServiceEntrySheetName, PostingDate (pre-approval) | After approval (workflow status changes), most fields lock | | A_PurgInfoRecdOrgPlantData | API_INFORECORD_PROCESS_SRV | Status fields, validity dates | TRAP: NetPriceAmount returns 204 but DOES NOT change — see known-traps.md. Price is in to_PurInfoRecdPrcgCndnValidity | | A_SupplierPurchasingOrg | API_BUSINESS_PARTNER | PaymentTerms, PurchaseOrderCurrency | Composite key: Supplier + PurchasingOrganization | | A_SupplierCompany | API_BUSINESS_PARTNER | PaymentTerms, ReconciliationAccount | Composite key: Supplier + CompanyCode |
Entity-disabled (PATCH always fails)
Don't even try — these entities reject all PATCH with 405 CX_SADL_ENTITY_CUD_DISABLED. They need cancel-and-recreate flows:
| Entity | Why | Alternative | |---|---|---| | A_SupplierInvoice | Posted invoices are immutable for audit | Cancel via Cancel_SupplierInvoice action + create new | | A_MaterialDocumentHeader (GR) | Material movements are immutable for inventory integrity | Reverse via movement type 102 (creates negative MatDoc) | | A_Product | Cloud Public locks Product master to MDG | Update via A_ProductDescription for description; A_ProductPlant for plant data |
Reference files
references/mutable-fields-catalog.md— full per-entity table of what worksreferences/known-traps.md— the "204 looks like success but didn't change" casesreferences/key-resolution.md— how to construct keys for composite-key entities (PIR org-plant, BP address, etc.)references/cancel-vs-update.md— when to cancel-recreate vs PATCHscripts/safe-update.mjs— reference implementation with GET-PATCH-GET verify flow + dry-run mode
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ilia-inovaflow
- Source: ilia-inovaflow/s4hana-create-record-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.