diff --git a/.gitignore b/.gitignore
index 133ae09859ba003be1a2a45e01c67bb65307f54a..3e3d850ca7100199bb3f35f92b827f8958d67fda 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 .envrc.local
 coverage
+terraform*.log
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 86d70e5785c8d50742c796a651d7f56314b6a9a8..9185746af2eb5cf253b555add72703369e757eb4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -120,6 +120,13 @@ review_destroy:
     name: review/$CI_COMMIT_REF_SLUG
     action: stop
   dependencies: [] # explicitly disable artifact usage
+  artifacts:
+    paths:
+      - "terraform/environments/crash.log" # optional, only available in case of a crash/panic
+      - "terraform/environments/terraform-*.log" # separate log for every step/command
+    name: "${CI_JOB_NAME}_${CI_JOB_ID}"
+    when: on_failure
+    expire_in: 1 week
   script:
     # branch may have been deleted, so we clone and checkout main
     - git clone $CI_REPOSITORY_URL main-clone
diff --git a/app/server.ts b/app/server.ts
index a29ef80921cb70c1105676214c8bd130121ab993..51049521dff21602af83216004c044f6710bee79 100644
--- a/app/server.ts
+++ b/app/server.ts
@@ -44,7 +44,7 @@ const typeDefs = `
       imageUrl: String
       imageUrlOriginal: String
       source: String
-      categories: [String]!
+      categories: [String]! @deprecated(reason: "Use topics & skills instead. (since v1.34)")
       organizer: Organizer
       location: String
       latitude: Float
@@ -63,17 +63,17 @@ const typeDefs = `
       """
       If this engagement is part of a recommendation query response, matchedTopics contains the slugs of matched topics. The order is defined as follows: 1) topics that were part of the query come first (sorted by matching confidence). 2) other topics the engagement matches (sorted by matching confidence)
       """
-      matchedTopics: [String] @deprecated(reason: "Use the identically named field in the EngagementReco type returned by the engagementRecos query instead.")
+      matchedTopics: [String] @deprecated(reason: "Use the identically named field in the EngagementReco type returned by the engagementRecos query instead. (since v1.34)")
       
       """
       If this engagement is part of a recommendation query response, matchedSkills contains the slugs of matched skills. The order is defined as follows: 1) skills that were part of the query come first (sorted by matching confidence). 2) other topics the engagement matches (sorted by matching confidence)
       """
-      matchedSkills: [String] @deprecated(reason: "Use the identically named field in the EngagementReco type returned by the engagementRecos query instead.")
+      matchedSkills: [String] @deprecated(reason: "Use the identically named field in the EngagementReco type returned by the engagementRecos query instead. (since v1.34)")
       
       """
       If this engagement is part of a recommendation query response, matchedGeoLocationDistanceInMeters contains the distance (in meters) between the engagement and the location given in the query.
       """
-      matchedGeoLocationDistanceInMeters: Float @deprecated(reason: "Use the identically named field in the EngagementReco type returned by the engagementRecos query instead.")
+      matchedGeoLocationDistanceInMeters: Float @deprecated(reason: "Use the identically named field in the EngagementReco type returned by the engagementRecos query instead. (since v1.34)")
     }
 
     type Category {
@@ -118,12 +118,12 @@ const typeDefs = `
 
     type Query {
         # uses offset-based pagination as described in https://www.apollographql.com/docs/react/pagination/offset-based
-        engagementOpportunities(offset: Int! = 0, limit: Int! = 10, location: GeoJSON, category: String): EngagementsResponse!
+        engagementOpportunities(offset: Int! = 0, limit: Int! = 10, location: GeoJSON, category: String): EngagementsResponse! @deprecated(reason: "Use the engagementRecos query instead. (since v1.34)")
         
         """
         Retrieves a list of recommendations based on the request parameters "topics", "skills" and "geoLocationId". "topics" contains slugs of the "Topic" type, "skills" contains slugs of the "Skill" type, "geoLocationId" contains the ID of a location as returned by the "geo_*" queries.
         """
-        engagementRecommendations(offset: Int! = 0, limit: Int! = 10, topics: [String!] = [], skills: [String!] = [], geolocationId: String): EngagementsResponse! @deprecated(reason: "Use the engagementRecos query instead.")
+        engagementRecommendations(offset: Int! = 0, limit: Int! = 10, topics: [String!] = [], skills: [String!] = [], geolocationId: String): EngagementsResponse! @deprecated(reason: "Use the engagementRecos query instead. (since v1.34)")
         
         """
         Retrieves a list of recommendations based on the request parameters "topics", "skills" and "geoLocationId". "topics" contains slugs of the "Topic" type, "skills" contains slugs of the "Skill" type, "geoLocationId" contains the ID of a location as returned by the "geo_*" queries.
@@ -132,7 +132,7 @@ const typeDefs = `
         
         engagement(id: String!): Engagement
         
-        categories: CategoriesResponse!
+        categories: CategoriesResponse! @deprecated(reason: "Not in use anymore since the introduction of the engagementRecos query (since v1.34)")
         
         filteredEngagements(offset: Int! = 0, limit: Int! = 10, topics: [String!] = [], skills: [String!] = [], geolocationId: String): EngagementsResponse!
     }
diff --git a/terraform/environments/scripts/destroy-env.sh b/terraform/environments/scripts/destroy-env.sh
index 8feb7be2d8fb66fdc05c7485e05a4091b8f97396..d8137694745e9a6bcded13142bf6a5e666b55e6c 100755
--- a/terraform/environments/scripts/destroy-env.sh
+++ b/terraform/environments/scripts/destroy-env.sh
@@ -1,4 +1,4 @@
-#!/usr/bin/env sh
+#!/usr/bin/env bash
 
 # exit when any command fails
 set -ex
@@ -6,11 +6,36 @@ set -ex
 # enable debug output in terraform
 export TF_LOG=DEBUG
 
-cd terraform/environments
+# retry logic for destroy: sometimes, a full workspace destroy does not work. This can be due to e.g.:
+# * implicit dependencies between terraform resources not declared with depends_on,
+# * unclean shutdown of resources, e.g. service does not close db connections, db still sees clients connected,
+# * GCP stuff not allowing our resources to be deleted.
+# Most of the time, retrying a destroy fixes these causes.
+retry() {
+  for i in {1..3}; do
+    set +e
+    "$@"
+    retval=$?
+    set -e
+    if [ "$retval" -ne "0" ]; then
+      if [ "$i" -lt "3" ]; then
+        echo "command '$*' failed in try $i, retrying after 60 seconds"
+        sleep 60 # let things settle a bit
+      else
+        echo "command '$*' failed in try $i, giving up"
+        exit $retval
+      fi
+    else
+      break # success
+    fi
+  done
+}
+
+cd "$(dirname "$0")"/..
 
 TF_LOG_PATH=terraform-init.log       terraform init
 TF_LOG_PATH=terraform-version.log    terraform version
-TF_LOG_PATH=terraform-workspace.log  terraform workspace new "$1" || terraform workspace select "$1"
-TF_LOG_PATH=terraform-destroy.log    terraform destroy -auto-approve -var="image_tag=dummy"
+TF_LOG_PATH=terraform-workspace.log  terraform workspace select -or-create=true "$1"
+TF_LOG_PATH=terraform-destroy.log    retry terraform destroy -auto-approve -var="image_tag=dummy"
 TF_LOG_PATH=terraform-ws-default.log terraform workspace select default
 TF_LOG_PATH=terraform-ws-delete.log  terraform workspace delete "$1"