diff --git a/README.md b/README.md
index de15bc49..6c3df64e 100644
--- a/README.md
+++ b/README.md
@@ -127,7 +127,7 @@ CAMUNDA_TOKEN_SCOPE
 
 Here is an example of doing this via the constructor, rather than via the environment:
 
-````typescript
+```typescript
 import { Camunda8 } from '@camunda8/sdk'
 
 const c8 = new Camunda8({
@@ -168,4 +168,20 @@ export CAMUNDA_CONSOLE_CLIENT_ID='e-JdgKfJy9hHSXzi'
 export CAMUNDA_CONSOLE_CLIENT_SECRET='DT8Pe-ANC6e3Je_ptLyzZvBNS0aFwaIV'
 export CAMUNDA_CONSOLE_BASE_URL='https://api.cloud.camunda.io'
 export CAMUNDA_CONSOLE_OAUTH_AUDIENCE='api.cloud.camunda.io'
-````
+```
+
+## Debugging
+
+The SDK uses the [`debug`](https://github.com/debug-js/debug) library. To enable debugging output, set a value for the `DEBUG` environment variable. The value is a comma-separated list of debugging namespaces. The SDK has the following namespaces:
+
+| Value                  | Component            |
+| ---------------------- | -------------------- |
+| `camunda:adminconsole` | Admin Console API    |
+| `camunda:modeler`      | Modeler API          |
+| `camunda:operate`      | Operate API          |
+| `camunda:optimize`     | Optimize API         |
+| `camunda:tasklist`     | Tasklist API         |
+| `camunda:oauth`        | OAuth Token Exchange |
+| `camunda:grpc`         | Zeebe gRPC channel   |
+| `camunda:worker`       | Zeebe Worker         |
+| `camunda:zeebeclient`  | Zeebe Client         |
diff --git a/package.json b/package.json
index 44fdea55..e4e06729 100644
--- a/package.json
+++ b/package.json
@@ -9,12 +9,12 @@
 		"compile": "tsc --project tsconfig.json",
 		"docs": "rm -rf ./docs && typedoc",
 		"test": "jest --detectOpenHandles --testPathIgnorePatterns integration local-integration disconnection multitenancy",
-		"test:integration": "jest --runInBand --testPathIgnorePatterns disconnection --testPathIgnorePatterns multitenancy --detectOpenHandles --verbose true -u",
-		"test:multitenancy": "jest --runInBand --testPathIgnorePatterns disconnection --testPathIgnorePatterns admin - --detectOpenHandles --verbose true -u",
+		"test:integration": "jest --runInBand --testPathIgnorePatterns disconnection --testPathIgnorePatterns __tests__/config --testPathIgnorePatterns multitenancy --detectOpenHandles --verbose true -u",
+		"test:multitenancy": "jest --runInBand --testPathIgnorePatterns disconnection --testPathIgnorePatterns admin --testPathIgnorePatterns __tests__/config - --detectOpenHandles --verbose true -u",
 		"test:local": "jest --runInBand --verbose true --detectOpenHandles local-integration -u",
-		"test:local-integration": "jest --runInBand --detectOpenHandles --verbose --testPathIgnorePatterns disconnection --testPathIgnorePatterns admin --testPathIgnorePatterns multitenancy -u",
-		"test:docker": "jest --runInBand --testPathIgnorePatterns disconnection local-integration --detectOpenHandles --verbose true",
-		"test:disconnect": "jest --runInBand --verbose true --detectOpenHandles disconnection",
+		"test:local-integration": "jest --runInBand --detectOpenHandles --verbose --testPathIgnorePatterns disconnection --testPathIgnorePatterns admin --testPathIgnorePatterns multitenancy --testPathIgnorePatterns __tests__/config -u",
+		"test:docker": "jest --runInBand --testPathIgnorePatterns disconnection --testPathIgnorePatterns __tests__/config local-integration --detectOpenHandles --verbose true",
+		"test:disconnect": "jest --runInBand --verbose true --detectOpenHandles --testPathIgnorePatterns __tests__/config disconnection",
 		"test&docs": "npm test && npm run docs",
 		"publish": "npm run build && npm run test && lerna publish",
 		"commit": "cz",
@@ -72,7 +72,12 @@
 		},
 		"preset": "ts-jest",
 		"testEnvironment": "node",
-		"globalSetup": "<rootDir>/src/lib/jest.globalSetup.ts"
+		"testPathIgnorePatterns": [
+			"/node_modules/",
+			"__tests__/config/*.ts"
+		],
+		"globalSetup": "<rootDir>/src/__tests__/config/jest.globalSetup.ts",
+		"globalTeardown": "<rootDir>/src/__tests__/config/jest.globalTeardown.ts"
 	},
 	"config": {
 		"commitizen": {
diff --git a/src/lib/jest.globalSetup.ts b/src/__tests__/config/jest.cleanup.ts
similarity index 73%
rename from src/lib/jest.globalSetup.ts
rename to src/__tests__/config/jest.cleanup.ts
index d581ac77..981edfc5 100644
--- a/src/lib/jest.globalSetup.ts
+++ b/src/__tests__/config/jest.cleanup.ts
@@ -9,36 +9,42 @@ require('tsconfig-paths').register()
 import { OperateApiClient } from 'operate'
 import { BpmnParser, ZeebeGrpcClient } from 'zeebe'
 
-export default async () => {
+export const cleanUp = async () => {
 	console.log('_dirname', __dirname)
 	console.log(process.cwd())
 	// Your cleanup process here.
 	console.log(
 		'Running global setup: cleanup test process instances before all tests...'
 	)
-	const filePath = path.join(__dirname, '..', '__tests__', 'testdata')
+	const filePath = path.join(__dirname, '..', 'testdata')
 	const files = fs
 		.readdirSync(filePath)
 		.map((file) => path.join(filePath, file))
 	const bpmn = BpmnParser.parseBpmn(files)
 	// eslint-disable-next-line @typescript-eslint/no-explicit-any
 	const processIds = (bpmn as any[]).map(
-		(b) => b['bpmn:definitions']?.['bpmn:process']?.['@_id']
+		(b) => b?.['bpmn:definitions']?.['bpmn:process']?.['@_id']
 	)
 	const operate = new OperateApiClient()
-	const zeebe = new ZeebeGrpcClient()
+	const zeebe = new ZeebeGrpcClient({
+		config: {
+			zeebeGrpcSettings: { ZEEBE_CLIENT_LOG_LEVEL: 'NONE' },
+		},
+	})
 	for (const id of processIds) {
 		if (id) {
 			const res = await operate.searchProcessInstances({
 				filter: { bpmnProcessId: id, state: 'ACTIVE' },
 			})
 			const instancesKeys = res.items.map((instance) => instance.key)
-			console.log(`Canceling ${instancesKeys.length} instances for ${id}`)
+			if (instancesKeys.length > 0) {
+				console.log(`Cancelling ${instancesKeys.length} instances for ${id}`)
+			}
 			for (const key of instancesKeys) {
 				try {
 					await zeebe.cancelProcessInstance(key)
 				} catch (e) {
-					console.log('Error canceling process instance', key)
+					console.log('Error cancelling process instance', key)
 					console.log(e)
 				}
 			}
diff --git a/src/__tests__/config/jest.globalSetup.ts b/src/__tests__/config/jest.globalSetup.ts
new file mode 100644
index 00000000..f68b8615
--- /dev/null
+++ b/src/__tests__/config/jest.globalSetup.ts
@@ -0,0 +1,3 @@
+import { cleanUp } from './jest.cleanup'
+
+export default async () => cleanUp()
diff --git a/src/__tests__/config/jest.globalTeardown.ts b/src/__tests__/config/jest.globalTeardown.ts
new file mode 100644
index 00000000..f68b8615
--- /dev/null
+++ b/src/__tests__/config/jest.globalTeardown.ts
@@ -0,0 +1,3 @@
+import { cleanUp } from './jest.cleanup'
+
+export default async () => cleanUp()
diff --git a/src/__tests__/tasklist/tasklist.integration.spec.ts b/src/__tests__/tasklist/tasklist.integration.spec.ts
index 9a3a7935..339a2b84 100644
--- a/src/__tests__/tasklist/tasklist.integration.spec.ts
+++ b/src/__tests__/tasklist/tasklist.integration.spec.ts
@@ -1,5 +1,6 @@
 import { join } from 'path'
 
+import { OperateApiClient } from 'operate'
 import {
 	CreateProcessInstanceResponse,
 	DeployResourceResponse,
@@ -42,7 +43,7 @@ describe('TasklistApiClient', () => {
 				interests: ['golf', 'frisbee'],
 			},
 		})
-		await delay(10000) // we wait here to allow Tasklist to do its thing
+		await delay(13000) // we wait here to allow Tasklist to do its thing
 	})
 
 	afterEach(async () => {
@@ -101,6 +102,14 @@ describe('TasklistApiClient', () => {
 	describe('Write operations', () => {
 		it('can claim a task', async () => {
 			const tasklist = new TasklistApiClient()
+			expect(p).toBeTruthy()
+			const operate = new OperateApiClient()
+			const res = await operate
+				.getProcessInstance(p!.processInstanceKey)
+				.catch((e) => {
+					console.log('Error getting process instance', e)
+				})
+			expect(res).toBeTruthy()
 			const tasks = await tasklist.searchTasks({ state: 'CREATED' })
 			const taskid = tasks[0].id
 			expect(tasks.length).toBeGreaterThan(0)
diff --git a/src/__tests__/zeebe/integration/Worker-Failure-Retries.spec.ts b/src/__tests__/zeebe/integration/Worker-Failure-Retries.spec.ts
index 513e1e93..4be2e8c5 100644
--- a/src/__tests__/zeebe/integration/Worker-Failure-Retries.spec.ts
+++ b/src/__tests__/zeebe/integration/Worker-Failure-Retries.spec.ts
@@ -52,9 +52,9 @@ test('Decrements the retries count by default', async () => {
 					return job.fail('Some reason')
 				}
 				expect(job.retries).toBe(99)
-				resolve(null)
 				return job.complete().then(async (res) => {
 					await worker.close()
+					resolve(null)
 					return res
 				})
 			},
diff --git a/src/__tests__/zeebe/multitenancy/createProcessInstance-mt.spec.ts b/src/__tests__/zeebe/multitenancy/createProcessInstance-mt.spec.ts
index a6663c32..ff6fbc61 100644
--- a/src/__tests__/zeebe/multitenancy/createProcessInstance-mt.spec.ts
+++ b/src/__tests__/zeebe/multitenancy/createProcessInstance-mt.spec.ts
@@ -56,7 +56,7 @@ test('Will throw an error if no tenantId is provided when starting a process ins
 			bpmnProcessId,
 			variables: {},
 		})
-		client.cancelProcessInstance(p.bpmnProcessId)
+		client.cancelProcessInstance(p.processInstanceKey)
 	} catch (e) {
 		// eslint-disable-next-line @typescript-eslint/no-explicit-any
 		expect((e as any).code).toBe(3)
diff --git a/src/admin/lib/AdminApiClient.ts b/src/admin/lib/AdminApiClient.ts
index b8b3980f..14eb4a50 100644
--- a/src/admin/lib/AdminApiClient.ts
+++ b/src/admin/lib/AdminApiClient.ts
@@ -14,7 +14,7 @@ import { IOAuthProvider } from '../../oauth'
 
 import * as Dto from './AdminDto'
 
-const debug = d('consoleapi')
+const debug = d('camunda:adminconsole')
 
 export class AdminApiClient {
 	private userAgentString: string
@@ -45,9 +45,20 @@ export class AdminApiClient {
 				certificateAuthority,
 			},
 			hooks: {
-				beforeRequest: [
-					(options: unknown) => {
-						debug('beforeRequest', options)
+				beforeError: [
+					(error) => {
+						const { request } = error
+						if (request) {
+							debug(`Error in request to ${request.options.url.href}`)
+							debug(
+								`Request headers: ${JSON.stringify(request.options.headers)}`
+							)
+							debug(`Error: ${error.code} - ${error.message}`)
+
+							// Attach more contextual information to the error object
+							error.message += ` (request to ${request.options.url.href})`
+						}
+						return error
 					},
 				],
 			},
diff --git a/src/modeler/lib/ModelerAPIClient.ts b/src/modeler/lib/ModelerAPIClient.ts
index 76a7732c..cccdc735 100644
--- a/src/modeler/lib/ModelerAPIClient.ts
+++ b/src/modeler/lib/ModelerAPIClient.ts
@@ -12,7 +12,7 @@ import { IOAuthProvider } from 'oauth'
 
 import * as Dto from './ModelerDto'
 
-const debug = d('modelerapi')
+const debug = d('camunda:modeler')
 
 const API_VERSION = 'v1'
 
@@ -43,6 +43,23 @@ export class ModelerApiClient {
 				certificateAuthority,
 			},
 			responseType: 'json',
+			hooks: {
+				beforeError: [
+					(error) => {
+						const { request } = error
+						if (request) {
+							debug(`Error in request to ${request.options.url.href}`)
+							debug(
+								`Request headers: ${JSON.stringify(request.options.headers)}`
+							)
+							debug(`Error: ${error.code} - ${error.message}`)
+							// Attach more contextual information to the error object
+							error.message += ` (request to ${request.options.url.href})`
+						}
+						return error
+					},
+				],
+			},
 		})
 		debug(`baseUrl: ${prefixUrl}`)
 	}
diff --git a/src/oauth/lib/NullAuthProvider.ts b/src/oauth/lib/NullAuthProvider.ts
index 6a30e00a..eb089bfd 100644
--- a/src/oauth/lib/NullAuthProvider.ts
+++ b/src/oauth/lib/NullAuthProvider.ts
@@ -3,7 +3,7 @@ import { IOAuthProvider } from 'oauth'
 
 import { TokenGrantAudienceType } from './IOAuthProvider'
 
-const d = debug('oauth')
+const d = debug('camunda:oauth')
 
 export class NullAuthProvider implements IOAuthProvider {
 	public async getToken(audience: TokenGrantAudienceType): Promise<string> {
diff --git a/src/operate/lib/OperateApiClient.ts b/src/operate/lib/OperateApiClient.ts
index 3a0506fe..977a54e3 100644
--- a/src/operate/lib/OperateApiClient.ts
+++ b/src/operate/lib/OperateApiClient.ts
@@ -1,3 +1,4 @@
+import { debug as d } from 'debug'
 import got from 'got'
 import {
 	CamundaEnvironmentConfigurator,
@@ -30,6 +31,8 @@ import { parseSearchResults } from './parseSearchResults'
 
 const OPERATE_API_VERSION = 'v1'
 
+const debug = d('camunda:operate')
+
 type JSONDoc = { [key: string]: string | boolean | number | JSONDoc }
 type EnhanceWithTenantIdIfMissing<T> = T extends {
 	filter: { tenantId: string | undefined }
@@ -91,6 +94,24 @@ export class OperateApiClient {
 			https: {
 				certificateAuthority,
 			},
+			hooks: {
+				beforeError: [
+					(error) => {
+						const { request } = error
+						if (request) {
+							debug(`Error in request to ${request.options.url.href}`)
+							debug(
+								`Request headers: ${JSON.stringify(request.options.headers)}`
+							)
+							debug(`Error: ${error.code} - ${error.message}`)
+
+							// Attach more contextual information to the error object
+							error.message += ` (request to ${request.options.url.href})`
+						}
+						return error
+					},
+				],
+			},
 		})
 		this.tenantId = config.CAMUNDA_TENANT_ID
 	}
diff --git a/src/optimize/lib/OptimizeApiClient.ts b/src/optimize/lib/OptimizeApiClient.ts
index 14b63c39..0442e421 100644
--- a/src/optimize/lib/OptimizeApiClient.ts
+++ b/src/optimize/lib/OptimizeApiClient.ts
@@ -1,4 +1,4 @@
-import d from 'debug'
+import { debug as d } from 'debug'
 import got from 'got'
 import {
 	CamundaEnvironmentConfigurator,
@@ -23,7 +23,7 @@ import {
 } from './APIObjects'
 import { ReportResults } from './ReportResults'
 
-const debug = d('optimizeapi')
+const debug = d('camunda:optimize')
 
 /**
  * @description The high-level API client for Optimize.
@@ -76,9 +76,20 @@ export class OptimizeApiClient {
 				certificateAuthority,
 			},
 			hooks: {
-				beforeRequest: [
-					(options: unknown) => {
-						debug('beforeRequest', options)
+				beforeError: [
+					(error) => {
+						const { request } = error
+						if (request) {
+							debug(`Error in request to ${request.options.url.href}`)
+							debug(
+								`Request headers: ${JSON.stringify(request.options.headers)}`
+							)
+							debug(`Error: ${error.code} - ${error.message}`)
+
+							// Attach more contextual information to the error object
+							error.message += ` (request to ${request.options.url.href})`
+						}
+						return error
 					},
 				],
 			},
diff --git a/src/tasklist/lib/TasklistApiClient.ts b/src/tasklist/lib/TasklistApiClient.ts
index f0331f8c..3daf0cc5 100644
--- a/src/tasklist/lib/TasklistApiClient.ts
+++ b/src/tasklist/lib/TasklistApiClient.ts
@@ -29,7 +29,7 @@ import {
 } from './TasklistDto'
 import { JSONDoc, encodeTaskVariablesForAPIRequest } from './utils'
 
-const trace = debug('tasklist:rest')
+const trace = debug('camunda:tasklist')
 
 const TASKLIST_API_VERSION = 'v1'
 
@@ -77,6 +77,24 @@ export class TasklistApiClient {
 			https: {
 				certificateAuthority,
 			},
+			hooks: {
+				beforeError: [
+					(error) => {
+						const { request } = error
+						if (request) {
+							debug(`Error in request to ${request.options.url.href}`)
+							debug(
+								`Request headers: ${JSON.stringify(request.options.headers)}`
+							)
+							debug(`Error: ${error.code} - ${error.message}`)
+
+							// Attach more contextual information to the error object
+							error.message += ` (request to ${request.options.url.href})`
+						}
+						return error
+					},
+				],
+			},
 		})
 		trace(`prefixUrl: ${prefixUrl}`)
 	}
@@ -228,9 +246,9 @@ export class TasklistApiClient {
 	/**
 	 * @description Assign a task with taskId to assignee or the active user.
 	 * @throws Status 400 - An error is returned when the task is not active (not in the CREATED state).
-	 * @throws Status 400 - An error is returned when task was already assigned, except the case when JWT authentication token used and allowOverrideAssignment = true.
-	 * @throws Status 403 - An error is returned when user doesn't have the permission to assign another user to this task.
-	 * @throws Status 404 - An error is returned when the task with the taskId is not found.
+	 * Status 400 - An error is returned when task was already assigned, except the case when JWT authentication token used and allowOverrideAssignment = true.
+	 * Status 403 - An error is returned when user doesn't have the permission to assign another user to this task.
+	 * Status 404 - An error is returned when the task with the taskId is not found.
 	 */
 	public async assignTask({
 		taskId,
diff --git a/src/zeebe/lib/GrpcClient.ts b/src/zeebe/lib/GrpcClient.ts
index effd6084..ebd09250 100644
--- a/src/zeebe/lib/GrpcClient.ts
+++ b/src/zeebe/lib/GrpcClient.ts
@@ -19,7 +19,7 @@ import { packageVersion } from './GetPackageVersion'
 import { GrpcError } from './GrpcError'
 import { Loglevel, ZBCustomLogger } from './interfaces-published-contract'
 
-const debug = d('grpc')
+const debug = d('camunda:grpc')
 
 export interface GrpcClientExtendedOptions {
 	longPoll?: MaybeTimeDuration
diff --git a/src/zeebe/lib/ZBWorkerBase.ts b/src/zeebe/lib/ZBWorkerBase.ts
index 41132b24..a927e9d9 100644
--- a/src/zeebe/lib/ZBWorkerBase.ts
+++ b/src/zeebe/lib/ZBWorkerBase.ts
@@ -24,7 +24,7 @@ import { ZBClientOptions } from './interfaces-published-contract'
 
 import { parseVariablesAndCustomHeadersToJSON } from '.'
 
-const debug = d('worker')
+const debug = d('camunda:worker')
 debug('Loaded ZBWorkerBase')
 
 const MIN_ACTIVE_JOBS_RATIO_BEFORE_ACTIVATING_JOBS = 0.3
diff --git a/src/zeebe/lib/cancelProcesses.ts b/src/zeebe/lib/cancelProcesses.ts
index e35fa1e0..416f26f5 100644
--- a/src/zeebe/lib/cancelProcesses.ts
+++ b/src/zeebe/lib/cancelProcesses.ts
@@ -6,27 +6,36 @@ export async function cancelProcesses(processDefinitionKey: string) {
 	if (!operate) {
 		return
 	}
-	const processes = await operate.searchProcessInstances({
-		filter: {
-			processDefinitionKey,
-		},
-	})
-	await Promise.all(
-		processes.items.map((item) => {
-			return operate.deleteProcessInstance(item.key).catch((e) => {
-				console.log(`Failed to delete process ${item.key}`)
-				console.log(e)
-			})
+	const processes = await operate
+		.searchProcessInstances({
+			filter: {
+				processDefinitionKey,
+			},
+		})
+		.catch((e) => {
+			console.log(
+				`Failed to search for process instances for ${processDefinitionKey}`
+			)
+			console.log(e)
 		})
-	)
+	if (processes) {
+		await Promise.all(
+			processes.items.map((item) => {
+				return operate.deleteProcessInstance(item.key).catch((e) => {
+					console.log(`Failed to delete process ${item.key}`)
+					console.log(e)
+				})
+			})
+		)
+	}
 }
 
 function createClient() {
 	try {
 		return new OperateApiClient()
 	} catch (e: unknown) {
-		// console.log(e.message)
-		// console.log(`Running without access to Operate`)
+		console.log((e as Error).message)
+		console.log(`Running without access to Operate`)
 		return null
 	}
 }
diff --git a/src/zeebe/zb/ZeebeGrpcClient.ts b/src/zeebe/zb/ZeebeGrpcClient.ts
index 5cd51aab..c8c0b5c4 100644
--- a/src/zeebe/zb/ZeebeGrpcClient.ts
+++ b/src/zeebe/zb/ZeebeGrpcClient.ts
@@ -42,7 +42,7 @@ import { Utils } from '../lib/utils'
 import { ZBBatchWorker } from './ZBBatchWorker'
 import { ZBWorker } from './ZBWorker'
 
-const debug = d('client')
+const debug = d('camunda:zeebeclient')
 
 const idColors = [
 	chalk.yellow,