vb6-error-handling — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited vb6-error-handling (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
VB6 has no try/catch. The model is On Error GoTo <label> with structured cleanup. The CSEH (Code Style for Error Handler) convention standardizes this pattern across the codebase so error paths are predictable, log output is consistent, and Erl (error line) is meaningful in production logs.
CSEH style is declared by a marker comment above the procedure signature:
'CSEH: ErrRaise
Public Function GetActiveCustomer(...) As ObjectThe <EhHeader> and <EhFooter> tags delimit regions that may be regenerated by external tooling (MZ-Tools or similar). Never alter the delimiter format or remove the tags — automation depends on them.
| Style | Marker | Handler behavior | Use for |
|---|---|---|---|
ErrRaise | 'CSEH: ErrRaise | Captures Err into locals, then re-raises the original Err.Number via Err.Raise ..., msg | Utility functions, library routines, any procedure called by other code |
MsgBox | 'CSEH: MsgBox | Displays critical dialog and returns to caller without re-raising | Top-level UI event handlers, command buttons, menu items |
Default for new code: `ErrRaise`. The MsgBox style is reserved for the outermost layer (event handlers) where errors must be surfaced to the user and the call stack ends.
Canonical structure for a function that propagates errors to its caller:
'CSEH: ErrRaise
Public Function GetActiveCustomer(ByVal lngCustomerID As Long) As Object
'<EhHeader>
On Error GoTo GetActiveCustomer_Err
EnterMethod "modDB", "GetActiveCustomer"
'</EhHeader>
Dim objCmd As Object
Dim objRs As Object
Dim strSQL As String
100 InitializeDBConnection
105 Set objCmd = CreateObject("ADODB.Command")
110 Set objCmd.ActiveConnection = mobjCnn
115 objCmd.CommandType = adCmdText
120 strSQL = "SELECT * FROM Customer WHERE idCustomer = ? AND custActive = 1"
125 objCmd.CommandText = strSQL
130 objCmd.Parameters.Append objCmd.CreateParameter("idCustomer", adInteger, adParamInput, , lngCustomerID)
135 Set objRs = objCmd.Execute
140 Set GetActiveCustomer = objRs
'<EhFooter>
On Error GoTo 0
GetActiveCustomer_Exit:
Set objCmd = Nothing
ExitMethod "modDB", "GetActiveCustomer"
Exit Function
GetActiveCustomer_Err:
' Capture Err state FIRST — the cleanup and ExitMethod below reset the Err object
Dim lGetActiveCustomer_ErrNum As Long
Dim sGetActiveCustomer_Err As String
lGetActiveCustomer_ErrNum = Err.Number
sGetActiveCustomer_Err = "Error: " & Err.Number & " - " & Err.Description & " (" & Erl & ")"
' clean up resources here
Set objCmd = Nothing
Set objRs = Nothing
ExitMethod "modDB", "GetActiveCustomer"
Err.Raise lGetActiveCustomer_ErrNum, "SampleApp.modDB.GetActiveCustomer", sGetActiveCustomer_Err
'</EhFooter>
End Function'CSEH: ErrRaiseOn Error GoTo <Func>_Err andEnterMethod "<module>", "<Func>"
<Func>_Exit (success path)<Func>_Err (error path)_Err label
<Func>_Err copyErr.Number into a local and build the message string before any other statement runs. Cleanup, ExitMethod, object teardown, or any procedure call resets Err (see "Capture Err before cleanup" below)
& " (" & Erl & ")"`
Set ... = Nothing happens beforeErr.Raise, not after (after is unreachable)
trace stack consistent
<Application>.<Module>.<Procedure>original Err.Number captured in invariant 5; reserve vbObjectError + <offset> for errors the procedure originates itself (see section 5)
The opening statements of the _Err handler must copy Err.Number and Err.Description into locals. Any statement that runs afterwards — a Set obj = Nothing (which fires the object's Class_Terminate), ExitMethod, closing a recordset, or any call to a procedure that does its own error handling — can reset the Err object to 0 / "". Referencing Err.Number or Err.Description directly in the final Err.Raise, after cleanup has run, re-raises a blank error and the real failure is lost.
❌ Wrong — `Err` is read in `Err.Raise` after cleanup has already cleared it:
BuildPagamentos_Err:
Set rsPag = Nothing
ExitMethod "clsFiscalApiEmitterNFe", "BuildPagamentos"
' Err.Number / Err.Description are 0 / "" by now — a blank error is re-raised
Err.Raise Err.Number, "AutomatizeSis.clsFiscalApiEmitterNFe.BuildPagamentos", Err.Description✅ Right — capture into locals first, then clean up, then raise:
BuildPagamentos_Err:
Dim lBuildPagamentos_ErrNum As Long
Dim sBuildPagamentos_Err As String
lBuildPagamentos_ErrNum = Err.Number
sBuildPagamentos_Err = "Error: " & Err.Number & " - " & Err.Description & " (" & Erl & ")"
Set rsPag = Nothing
ExitMethod "clsFiscalApiEmitterNFe", "BuildPagamentos"
Err.Raise lBuildPagamentos_ErrNum, "AutomatizeSis.clsFiscalApiEmitterNFe.BuildPagamentos", sBuildPagamentos_ErrThe same rule applies to the MsgBox style: build the message string from Err before MsgBoxCritical and before falling through to _Exit (where ExitMethod runs).
For top-level procedures where errors terminate at the UI:
'CSEH: MsgBox
Public Sub cmdSave_Click()
'<EhHeader>
On Error GoTo cmdSave_Click_Err
EnterMethod "frmCustomer", "cmdSave_Click"
'</EhHeader>
' ... UI logic that calls other CSEH:ErrRaise functions ...
'<EhFooter>
cmdSave_Click_Exit:
ExitMethod "frmCustomer", "cmdSave_Click"
Exit Sub
cmdSave_Click_Err:
Dim scmdSave_Click_Err As String
scmdSave_Click_Err = Err.Description & vbCrLf & _
"in SampleApp.frmCustomer.cmdSave_Click at line " & Erl
MsgBoxCritical scmdSave_Click_Err
GoTo cmdSave_Click_Exit
'</EhFooter>
End SubMsgBoxCritical (or equivalent project wrapper) displays the error to theuser
Err.Raise at the end — the error stops hereGoTo <Func>_Exit after the dialog so cleanup and ExitMethod still runErl is a built-in function returning the line number where the last error occurred — but only if lines are numbered. Without numbering, Erl returns 0 and traceability is lost.
Dim, no comments, no labels, no<EhHeader>/<EhFooter> tags)
Dim strSQL As String
100 strSQL = ""
105 strSQL = strSQL & "SELECT idCustomer, custName " & vbNewLine
110 strSQL = strSQL & " FROM Customer " & vbNewLine
115 strSQL = strSQL & " WHERE custActive = 1 " & vbNewLine
120 objCmd.CommandText = strSQL
125 Set objRs = objCmd.ExecuteNumber any procedure with:
On Error GoTo (CSEH style)Skip numbering for:
Get/Let that just reads/assigns a fieldTwo different situations call Err.Raise, and they choose the number differently:
_Err handler re-raising what itcaught): re-raise the original `Err.Number`, captured into a local before cleanup (see section 2). This keeps the true error code intact as it travels up the stack, so the outermost handler logs the real failure — not a re-stamped offset.
e.g. a failed validation or an empty mandatory recordset): use vbObjectError + <offset>, where <offset> identifies the condition. Here the procedure is setting Err, so there is nothing to capture first.
' Originating — the procedure decides this is an error:
125 If objRs.EOF Then Err.Raise vbObjectError + 101, "SampleApp.modDB.GetActiveCustomer", "Customer not found"For originated errors, the offset is chosen per procedure (or per error category, if the codebase catalogs them):
| Approach | Pattern |
|---|---|
| Per-procedure unique number | Each procedure gets its own offset |
| Per-category (validation, db, IO) | Range allocation (e.g., 100-199 validation, 200-299 db) |
| Fixed offset | Single offset across the codebase (less informative) |
In all cases the qualified source string (SampleApp.modDB.GetActiveCustomer) carries the procedure identity, and the EnterMethod/ExitMethod trace stack records the full call chain independently.
On Error Resume Next is forbidden in domain code. It silently swallows errors and produces invisible failures. One legitimate exception: cleanup and dispose routines, where the operation must complete even when individual steps fail.
Public Sub CloseDBConnection()
On Error Resume Next ' OK: cleanup
If Not mobjCnn Is Nothing Then
If mobjCnn.State <> 0 Then mobjCnn.Close
End If
Set mobjCnn = Nothing
End SubOther acceptable uses:
Not acceptable:
Every CSEH procedure calls both:
EnterMethod "modDB", "GetActiveCustomer" ' in <EhHeader>
ExitMethod "modDB", "GetActiveCustomer" ' in both _Exit and _ErrCalling ExitMethod only on the success path leaves the trace stack with the function "still open" after an error, corrupting future trace output. The error label must also call ExitMethod before the Err.Raise.
See vb6-trace-pattern for the trace mechanism details.
Resource cleanup (Set objX = Nothing, closing recordsets, closing files) appears in both the _Exit and _Err labels. Code paths must converge on freed resources regardless of outcome.
Common pattern: extract cleanup into a helper or duplicate the lines in both labels. The CSEH _Exit/_Err structure does not provide an automatic finally — discipline is on the author.
| Anti-pattern | Why it breaks |
|---|---|
On Error Resume Next around a For loop with DB calls | Silently skips failed rows, produces partial state with no log |
Err.Raise without cleanup before it | Recordsets and connections leak |
Err.Raise Err.Number, ..., Err.Description after cleanup/ExitMethod | Err was already reset to 0/"" — a blank error propagates and the real failure is lost |
ExitMethod only in _Exit | Trace stack grows monotonically; future traces are wrong |
Unnumbered procedure with Erl in the handler | Erl returns 0; production logs lose the line |
Empty Err.Description re-raised | Caller sees no message; first failure point is unidentifiable |
Missing Exit Sub/Exit Function before _Err label | Execution falls through into the error handler on success |
Modifying <EhHeader>/<EhFooter> tag format | Breaks regeneration tools (MZ-Tools or similar) |
'CSEH: ErrRaise or'CSEH: MsgBox)
<EhHeader> and <EhFooter> tags present and unmodifiedOn Error GoTo <Func>_Err is the first line inside <EhHeader>EnterMethod called in <EhHeader>_Exit and _Err labels exist_Exit falls through to Exit Sub/Exit Function, not into _ErrErr.Number, Err.Description, and ErlErr.Number/Err.Description captured into locals before anycleanup or ExitMethod; the re-raise uses the captured values (the original number when propagating), never Err.Number/Err.Description read after cleanup
Err.Raise (or before MsgBoxCritical inMsgBox style)
ExitMethod is called in both labelsErr.Raise source string is <App>.<Module>.<Procedure>On Error Resume Next outside cleanup/dispose~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.