diff --git a/product/CMFActivity/ActivityTool.py b/product/CMFActivity/ActivityTool.py
index eeb86a1de245fc1b523e848b55346bd7019a04df..59a51672a7b93de178708fbc4eb2b72cdacf6ccb 100644
--- a/product/CMFActivity/ActivityTool.py
+++ b/product/CMFActivity/ActivityTool.py
@@ -856,6 +856,12 @@ class ActivityTool (Folder, UniqueObject):
         if not acquired:
           return
 
+        # make sure our skin is set-up. On CMF 1.5 it's setup by acquisition,
+        # but on 2.2 it's by traversal, and our site probably wasn't traversed
+        # by the timerserver request, which goes into the Zope Control_Panel
+        # calling it a second time is a harmless and cheap no-op.
+        # both setupCurrentSkin and REQUEST are acquired from containers.
+        self.setupCurrentSkin(self.REQUEST)
         try:
           old_sm = getSecurityManager()
           try:
diff --git a/product/ERP5/Tool/AlarmTool.py b/product/ERP5/Tool/AlarmTool.py
index ffdac3b1d67cba1e2923474bfe63847f087bd852..cb08741956087ef55dabad72a3d1252aa3cce03a 100644
--- a/product/ERP5/Tool/AlarmTool.py
+++ b/product/ERP5/Tool/AlarmTool.py
@@ -205,6 +205,12 @@ class AlarmTool(BaseTool):
     acquired = last_tic_lock.acquire(0)
     if not acquired:
       return
+    # make sure our skin is set-up. On CMF 1.5 it's setup by acquisition,
+    # but on 2.2 it's by traversal, and our site probably wasn't traversed
+    # by the timerserver request, which goes into the Zope Control_Panel
+    # calling it a second time is a harmless and cheap no-op.
+    # both setupCurrentSkin and REQUEST are acquired from containers.
+    self.setupCurrentSkin(self.REQUEST)
     try:
       # only start when we are the alarmNode
       alarmNode = self.getAlarmNode()