| name | sap-unused-code |
| description | Find Z/Y custom objects never called at runtime by combining SAP call-monitor (SCMON) usage data with static where-used analysis — classifies each as UNUSED/LIKELY_UNUSED/USED/INDETERMINATE. Use when asked to "find unused custom code", "identify retirement candidates", "dead Z objects", or "what can we delete safely". |
SAP Unused Code Discovery
Find Z/Y custom objects that are never called at runtime — the prerequisite for any credible custom-code retirement project. Combines runtime usage data from SAP's call monitor (SCMON) with static where-used analysis to classify each object as UNUSED / LIKELY_UNUSED / USED / INDETERMINATE.
⚠ Required: scope and intent
This skill refuses to run without a filter. A "list all unused code" report across an ECC system returns tens of thousands of rows and is useless. The user must provide at least one of:
- Package (e.g.,
Z_FI_CUSTOM) — analyze only objects in this package
- Namespace prefix (e.g.,
Z_SALES_*, /ACME/*) — only objects matching the pattern
- Object list — comma-separated names to check
- Intent statement — e.g., "find Z reports in FI-GL that haven't run in 90 days" — narrow by purpose
If none is given, ask. Example clarifying questions:
Which package or namespace do you want to audit? E.g., "the Z_REPORTING package", "all Z* starting with Z_FI_", or "these 15 reports I'm planning to delete".
Why this is harder than it looks (read before proposing a plan)
SAP has two relevant monitoring systems. Understand which you're working with:
| System | What it is | Where data lives | Latency | Data lifetime |
|---|
| SCMON (ABAP Call Monitor) | Raw per-call counters | SCMON_DATA + SCMON_PROG + SCMON_SUB + SCMON_RDATA tables | Real-time while active | ~7 days rolling |
| SUSG (Usage Statistics Generator) | Daily aggregation over SCMON | SUSG_DATA + SUSG_PROG + SUSG_RDATA + SUSG_SUB + SUSG_ADMIN tables (or CDS views SUSG_I_DATA, SUSG_I_RDATA, SUSG_I_ODATA) | Depends on batch schedule (default ~02:30 daily) | Months/years |
Key constraint: SCMON auto-deactivates when record threshold is hit (SCMON_CONF_SET_MAX_NUM_SLICES). SUSG refuses to aggregate if SCMON was inactive during the window. On dev/test systems this breaks the pipeline constantly.
Three Options (pick based on system state)
Option A (preferred) — Direct SCMON table join via ARC-1
Works immediately on any system where SCMON has ever been active, regardless of whether SUSG aggregation ran.
SELECT p~PROGNAME, p~OBJECT, p~OBJ_NAME, SUM( d~COUNTER ) AS EXECS
FROM SCMON_DATA AS d INNER JOIN SCMON_PROG AS p ON d~TRIGID = p~PROGID
WHERE <filter>
GROUP BY p~PROGNAME, p~OBJECT, p~OBJ_NAME
ORDER BY EXECS DESCENDING
SELECT s~ROOTNAME, s~ROOTTYPE, SUM( d~COUNTER ) AS EXECS
FROM SCMON_DATA AS d INNER JOIN SCMON_SUB AS s ON d~SUBID = s~SUBID
GROUP BY s~ROOTNAME, s~ROOTTYPE
Column caveat (SAP naming is inverted):
SCMON_DATA.TRIGID → SCMON_PROG.PROGID (the executed program, despite the name)
SCMON_DATA.SUBID → SCMON_SUB.SUBID (the trigger root, transaction/report that initiated)
Pros: No batch wait, no slice rotation dance, runs today.
Cons: Limited retention window (~7 days); useless for "has this run in the last 6 months" questions.
Option B — Aggregated SUSG via CDS views
Use when the customer has SUSG properly scheduled (production systems usually do).
SELECT PROGNAME, OBJ_TYPE, OBJ_NAME, SUM( COUNTER ) AS EXECS, MAX( LAST_USED ) AS LAST_SEEN
FROM SUSG_I_DATA
WHERE <filter>
GROUP BY PROGNAME, OBJ_TYPE, OBJ_NAME
Pre-joined CDS view: SUSG_I_DATA, SUSG_I_RDATA, SUSG_I_ODATA. All have @AccessControl.authorizationCheck: #NOT_REQUIRED.
Pros: Longer history (months), pre-joined (simpler SQL), officially supported data.
Cons: Requires SUSG batch to have run (0 rows in SUSG until it does); nothing works if SCMON was deactivated during the window.
Option C — Manual SUSG XML export (fallback only)
If the customer has already run transaction SUSG and exported the 5 XML files (ADMIN0001.xml, PROG0001.xml, DATA*.xml, RDATA*.xml, SUB0001.xml), the skill can parse those locally. This is the Kiro path and should be the last resort — it requires human action in SAP GUI and offline file handoff.
This skill does not bundle an XML parser. If the user insists on Option C, tell them to use Kiro's parse-susg.py or write a local Python script; ARC-1 won't do it.
Smart Defaults (apply silently, do NOT ask)
| Setting | Default | Rationale |
|---|
| Option selection | Probe SCMON activity first; use A if live data exists, else B | Most systems have one or the other |
| Output classification | UNUSED / LIKELY_UNUSED / USED / INDETERMINATE | Kiro's 4-way classification; useful for prioritization |
| Where-used check | Yes, for non-runtime-seen objects | Static references are a real signal |
| Reporting granularity | Per-object with counts; top-20 + summary | Full list available on request |
Prerequisites
Check these up front. If missing, stop and tell the user.
SAP_ALLOW_FREE_SQL=true on the ARC-1 server (otherwise SAPQuery is blocked — it runs FreeSQL). Ask admin if needed.
- User has
S_TABU_NAM authorization on SCMON_DATA, SCMON_PROG, SCMON_SUB, SCMON_RDATA (or the SUSG equivalents for Option B).
- SCMON has been active at some point (Option A) OR SUSG aggregation has run (Option B). Probe with:
SAPQuery(sql="SELECT COUNT(*) AS CNT FROM SCMON_DATA")
SAPQuery(sql="SELECT COUNT(*) AS CNT FROM SUSG_I_DATA")
Zero rows in both → data collection hasn't happened. Point the user to the next section and stop.
If the customer has no SCMON/SUSG data yet
Tell them:
- SAP GUI: transaction
SCMON → activate the monitor → let it run for N days (7 is a common minimum)
- Transaction
SCMONL shows slice state; SCMOND shows raw data
- For permanent data: schedule
SCMON_COLLECT as a daily job, then SUSG_COLLECT_FROM_SCMON after it
- Re-run this skill once data is flowing
Step 1: Probe which option to use
Run the two COUNT queries above. Decision table:
| SCMON_DATA count | SUSG_I_DATA count | Use |
|---|
| > 0 | > 0 | A (fresher data, real-time) — but mention B is also available for longer history |
| > 0 | 0 | A (SUSG hasn't aggregated yet) |
| 0 | > 0 | B (SCMON expired; SUSG has history) |
| 0 | 0 | Stop. Tell user to activate SCMON; don't guess. |
Step 2: Enumerate the scope
Based on user's filter input:
2a. Package
SAPRead(type="DEVC", name="<package>")
Keep only objects of runtime types (PROG, CLAS, FUGR, FUNC) with Z/Y prefix. (Tables, CDS views, interfaces aren't "called at runtime" in a way SCMON/SUSG captures directly.)
2b. Namespace / prefix
SAPSearch takes a single objectType filter — call it once per runtime type and union the results:
SAPSearch(query="<prefix>*", objectType="PROG")
SAPSearch(query="<prefix>*", objectType="CLAS")
SAPSearch(query="<prefix>*", objectType="FUGR")
2c. Object list
Use names as given.
Call this set S (the candidate list).
Step 3: Query runtime usage
Option A (SCMON direct join)
Build a WHERE clause from set S. SAP FreeSQL has query length limits, so chunk if |S| > 100.
SELECT p~PROGNAME, p~OBJECT, p~OBJ_NAME, SUM( d~COUNTER ) AS EXECS
FROM SCMON_DATA AS d INNER JOIN SCMON_PROG AS p ON d~TRIGID = p~PROGID
WHERE p~OBJ_NAME IN ( 'ZCL_FOO', 'ZR_BAR', ... )
GROUP BY p~PROGNAME, p~OBJECT, p~OBJ_NAME
Also check trigger-side (some Z-objects run only as transactions/RFCs, never as inner callees):
SELECT s~ROOTNAME, s~ROOTTYPE, SUM( d~COUNTER ) AS EXECS
FROM SCMON_DATA AS d INNER JOIN SCMON_SUB AS s ON d~SUBID = s~SUBID
WHERE s~ROOTNAME IN ( ... )
Union the results. Build set U (runtime-used objects with counts).
Option B (SUSG)
SELECT PROGNAME, OBJ_TYPE, OBJ_NAME, SUM( COUNTER ) AS EXECS, MAX( LAST_USED ) AS LAST_SEEN
FROM SUSG_I_DATA
WHERE <scope filter>
GROUP BY PROGNAME, OBJ_TYPE, OBJ_NAME
Build set U with additional LAST_SEEN timestamp.
Step 4: Static where-used for objects not in U
For each object in S but not in U:
SAPNavigate(action="references", type="<type>", name="<name>")
(references is ARC-1's where-used action — it calls SAP's where-used scope API under the hood.)
Build set W (statically referenced objects — someone calls them in source, even if no one ran them in the observed window).
Step 5: Classify
Apply first-match-wins:
| Object in … | Classification | Meaning |
|---|
| U (runtime executed ≥ 1 time) | USED | Runs in production; keep |
| W (referenced in source, not in U) | LIKELY_UNUSED | Static callers exist but nothing exercised it in the observed window |
| Neither U nor W | UNUSED | Strong deletion candidate |
| S but object doesn't exist on system (e.g., SAPSearch couldn't resolve it) | INDETERMINATE | Manual review |
Step 6: Emit the report
6a. Headline
Unused Code Audit — <scope>
Data source: SCMON (2,843 rows, window: 2026-04-12 → 2026-04-19) ← Option A
— or —
Data source: SUSG (Aggregated 2026-01-01 → 2026-04-19, 128 days) ← Option B
Scope: 42 objects
USED: 18 (43%) — safe to keep
LIKELY_UNUSED: 12 (29%) — still has static callers; investigate
UNUSED: 10 (24%) — deletion candidates
INDETERMINATE: 2 (5%) — manual review
6b. UNUSED table (the deletion candidates)
Object Type Package Last change (TR)
ZR_OLD_FI_POSTING PROG Z_FI_CUSTOM 2024-02-15 (MARIAN / A4HK900042)
ZCL_LEGACY_HELPER CLAS Z_FI_CUSTOM 2023-11-03 (MARIAN / A4HK900031)
...
"Last change" comes from SAPRead(type="VERSIONS"). Old + unused = safest to delete.
6c. LIKELY_UNUSED table with caller hints
For each LIKELY_UNUSED object, show who references it:
ZCL_OLD_SALES_UTILS (CLAS)
Called by: ZR_SALES_JOB (PROG — also UNUSED — cascade delete)
ZCL_ACTIVE_HANDLER (CLAS — USED at runtime) ← blocks deletion
A LIKELY_UNUSED object whose callers are all UNUSED is transitively deletable. One active caller blocks deletion.
6d. Follow-up options
- "Want to see the static reference graph as a tree?" (→ chained
SAPNavigate where-used)
- "Want to clean-core-check the USED objects before worrying about unused ones?" (→ sap-clean-core-atc)
- "Want documentation for the USED objects?" (→ sap-object-documenter)
- "Want to turn this into a reviewed S/4HANA migration dossier with HTML/CSV/graph outputs?" (→ sap-migration-dossier)
- "Want to start deleting UNUSED objects?" → Manual: this skill does not delete anything; the user does it via
SAPWrite or a transport
Error Handling
| Symptom | Root cause | Fix |
|---|
SCMON_DATA count = 0, SUSG_I_DATA count = 0 | Monitoring never activated | Stop; instruct user to activate SCMON (Step above) |
SCMON has data but all SLICEID=1 and SUSG is 0 | Slice hasn't rotated; SUSG can't aggregate open slices | Use Option A (direct), skip SUSG; or run SCMON_COLLECT_ALL via SA38 then SUSG_COLLECT_FROM_SCMON |
SUSG_LOG shows "SCMON not active" | SCMON auto-deactivated (record threshold hit) | Raise threshold via SCMON_CONF_SET_MAX_NUM_SLICES, reactivate SCMON, accept you lost one aggregation cycle |
SAP_ALLOW_FREE_SQL=false | Admin policy | Either get it relaxed, or fall back to SAPRead(type="TABLE_CONTENTS", name="SCMON_DATA", maxRows=…) (also blocked by SAP_ALLOW_DATA_PREVIEW=false; no joins either way) — badly degrades the report |
403 on SCMON_* / SUSG_* tables | Missing S_TABU_NAM auth | User needs auth assignment; document tables in request |
| Scope returns > 500 candidates | User's filter too broad | Refuse; ask user to narrow (a 500-object deletion list is not actionable) |
Caveats
What runtime data does NOT tell you
- Coverage gap: a report that runs every year-end won't appear in a week's worth of SCMON data. Always consider the observation window vs. the object's expected frequency.
- Dead-code-detection ≠ code elimination: an UNUSED object may be emergency/DR code, exam-period-only, or called by a scheduled job not active during the window. Cross-check with business owners before deletion.
- Dynamic calls:
CALL FUNCTION IN BACKGROUND, SUBMIT with dynamic program names, or CALL METHOD via RTTI may not attribute to the right PROGNAME in SCMON. Some false UNUSEDs are unavoidable.
What this skill does NOT do
- No deletion. Report only. Deletion must be a human decision, ideally via transport and CTS.
- No cross-system consolidation (DEV + QAS + PRD usage). That requires exports from each system; this skill reads one system at a time.
- No replacement suggestions for deprecated-but-used code. For that, use sap-clean-core-atc.
- No XML import (Option C). If the customer has SUSG XML dumps and nothing else, they need an offline parser.
When to Use This Skill
- Scoping a custom-code retirement project
- Before a migration — don't migrate what you don't use
- Post-project cleanup — removing code written for a feature that was rolled back
- License/compliance reviews requiring a dead-code inventory
- Combine with sap-clean-core-atc: migrate USED Level-A code, audit USED Level-C/D code, delete UNUSED code entirely