diff --git a/cypress/integration/vault.spec.js b/cypress/integration/vault.spec.js
index 71967128..1a1fe782 100644
--- a/cypress/integration/vault.spec.js
+++ b/cypress/integration/vault.spec.js
@@ -1,407 +1,427 @@
 /**
  * Copyright (C) 2019  The Software Heritage developers
  * See the AUTHORS file at the top-level directory of this distribution
  * License: GNU Affero General Public License version 3, or any later version
  * See top-level LICENSE file for more information
  */
 
 let vaultItems = [];
 
 const progressbarColors = {
   'new': 'rgba(128, 128, 128, 0.5)',
   'pending': 'rgba(0, 0, 255, 0.5)',
   'done': 'rgb(92, 184, 92)'
 };
 
 function checkVaultCookingTask(objectType) {
   cy.contains('button', 'Actions')
     .click();
 
   cy.contains('.dropdown-item', 'Download')
     .click();
 
   cy.contains('.dropdown-item', objectType)
     .click();
 
   cy.wait('@checkVaultCookingTask');
 }
 
 function updateVaultItemList(vaultUrl, vaultItems) {
   cy.visit(vaultUrl)
     .then(() => {
       // Add uncooked task to localStorage
       // which updates it in vault items list
       window.localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultItems));
     });
 }
 
 // Mocks API response : /api/1/vault/(:objectType)/(:hash)
 // objectType : {'directory', 'revision'}
 function genVaultCookingResponse(objectType, objectId, status, message, fetchUrl) {
   return {
     'obj_type': objectType,
     'id': 1,
     'progress_message': message,
     'status': status,
     'obj_id': objectId,
     'fetch_url': fetchUrl
   };
 };
 
 // Tests progressbar color, status
 // And status in localStorage
 function testStatus(taskId, color, statusMsg, status) {
   cy.get(`.swh-vault-table #vault-task-${taskId}`)
     .should('be.visible')
     .find('.progress-bar')
     .should('be.visible')
     .and('have.css', 'background-color', color)
     .and('contain', statusMsg)
     .then(() => {
       // Vault item with object_id as taskId should exist in localStorage
       const currentVaultItems = JSON.parse(window.localStorage.getItem('swh-vault-cooking-tasks'));
       const vaultItem = currentVaultItems.find(obj => obj.object_id === taskId);
 
       assert.isNotNull(vaultItem);
       assert.strictEqual(vaultItem.status, status);
     });
 }
 
 describe('Vault Cooking User Interface Tests', function() {
 
   before(function() {
     this.directory = this.origin[0].directory[0].id;
     this.directoryUrl = this.Urls.browse_directory(this.directory);
     this.vaultDirectoryUrl = this.Urls.api_1_vault_cook_directory(this.directory);
     this.vaultFetchDirectoryUrl = this.Urls.api_1_vault_fetch_directory(this.directory);
 
     this.revision = this.origin[1].revisions[0];
     this.revisionUrl = this.Urls.browse_revision(this.revision);
     this.vaultRevisionUrl = this.Urls.api_1_vault_cook_revision_gitfast(this.revision);
     this.vaultFetchRevisionUrl = this.Urls.api_1_vault_fetch_revision_gitfast(this.revision);
 
     vaultItems[0] = {
       'object_type': 'revision',
       'object_id': this.revision,
       'email': '',
       'status': 'done',
       'fetch_url': `/api/1/vault/revision/${this.revision}/gitfast/raw/`,
       'progress_message': null
     };
   });
 
   beforeEach(function() {
     this.genVaultDirCookingResponse = (status, message = null) => {
       return genVaultCookingResponse('directory', this.directory, status,
                                      message, this.vaultFetchDirectoryUrl);
     };
 
     this.genVaultRevCookingResponse = (status, message = null) => {
       return genVaultCookingResponse('revision', this.revision, status,
                                      message, this.vaultFetchRevisionUrl);
     };
 
     cy.server();
   });
 
   it('should create a directory cooking task and report its status', function() {
 
     // Browse a directory
     cy.visit(this.directoryUrl);
 
     // Stub responses when requesting the vault API to simulate
     // a task has been created
 
     cy.route({
       method: 'GET',
       url: this.vaultDirectoryUrl,
       response: {'exception': 'NotFoundExc'}
     }).as('checkVaultCookingTask');
 
     cy.route({
       method: 'POST',
       url: this.vaultDirectoryUrl,
       response: this.genVaultDirCookingResponse('new')
     }).as('createVaultCookingTask');
 
     checkVaultCookingTask('as tarball');
 
     cy.route({
       method: 'GET',
       url: this.vaultDirectoryUrl,
       response: this.genVaultDirCookingResponse('new')
     }).as('checkVaultCookingTask');
 
     // Create a vault cooking task through the GUI
     cy.get('.modal-dialog')
       .contains('button:visible', 'Ok')
       .click();
 
     cy.wait('@createVaultCookingTask');
 
     // Check that a redirection to the vault UI has been performed
     cy.url().should('eq', Cypress.config().baseUrl + this.Urls.browse_vault());
 
     cy.wait('@checkVaultCookingTask').then(() => {
       testStatus(this.directory, progressbarColors['new'], 'new', 'new');
     });
 
     // Stub response to the vault API indicating the task is processing
     cy.route({
       method: 'GET',
       url: this.vaultDirectoryUrl,
       response: this.genVaultDirCookingResponse('pending', 'Processing...')
     }).as('checkVaultCookingTask');
 
     cy.wait('@checkVaultCookingTask').then(() => {
       testStatus(this.directory, progressbarColors['pending'], 'Processing...', 'pending');
     });
 
     // Stub response to the vault API indicating the task is finished
     cy.route({
       method: 'GET',
       url: this.vaultDirectoryUrl,
       response: this.genVaultDirCookingResponse('done')
     }).as('checkVaultCookingTask');
 
     cy.wait('@checkVaultCookingTask').then(() => {
       testStatus(this.directory, progressbarColors['done'], 'done', 'done');
     });
 
     // Stub response to the vault API to simulate archive download
     cy.route({
       method: 'GET',
       url: this.vaultFetchDirectoryUrl,
       response: `fx:${this.directory}.tar.gz,binary`,
       headers: {
         'Content-disposition': `attachment; filename=${this.directory}.tar.gz`,
         'Content-Type': 'application/gzip'
       }
     }).as('fetchCookedArchive');
 
     cy.get(`#vault-task-${this.directory} .vault-dl-link button`)
       .click();
 
     cy.wait('@fetchCookedArchive').then((xhr) => {
       assert.isNotNull(xhr.response.body);
     });
 
   });
 
   it('should create a revision cooking task and report its status', function() {
     // Browse a revision
     cy.visit(this.revisionUrl);
 
     // Stub responses when requesting the vault API to simulate
     // a task has been created
 
     cy.route({
       method: 'GET',
       url: this.vaultRevisionUrl,
       response: {'exception': 'NotFoundExc'}
     }).as('checkVaultCookingTask');
 
     cy.route({
       method: 'POST',
       url: this.vaultRevisionUrl,
       response: this.genVaultRevCookingResponse('new')
     }).as('createVaultCookingTask');
 
     // Create a vault cooking task through the GUI
     checkVaultCookingTask('as git');
 
     cy.route({
       method: 'GET',
       url: this.vaultRevisionUrl,
       response: this.genVaultRevCookingResponse('new')
     }).as('checkVaultCookingTask');
 
     // Create a vault cooking task through the GUI
     cy.get('.modal-dialog')
       .contains('button:visible', 'Ok')
       .click();
 
     cy.wait('@createVaultCookingTask');
 
     // Check that a redirection to the vault UI has been performed
     cy.url().should('eq', Cypress.config().baseUrl + this.Urls.browse_vault());
 
     cy.wait('@checkVaultCookingTask').then(() => {
       testStatus(this.revision, progressbarColors['new'], 'new', 'new');
     });
 
     // Stub response to the vault API indicating the task is processing
     cy.route({
       method: 'GET',
       url: this.vaultRevisionUrl,
       response: this.genVaultRevCookingResponse('pending', 'Processing...')
     }).as('checkVaultCookingTask');
 
     cy.wait('@checkVaultCookingTask').then(() => {
       testStatus(this.revision, progressbarColors['pending'], 'Processing...', 'pending');
     });
 
     // Stub response to the vault API indicating the task is finished
     cy.route({
       method: 'GET',
       url: this.vaultRevisionUrl,
       response: this.genVaultRevCookingResponse('done')
     }).as('checkVaultCookingTask');
 
     cy.wait('@checkVaultCookingTask').then(() => {
       testStatus(this.revision, progressbarColors['done'], 'done', 'done');
     });
 
     // Stub response to the vault API indicating to simulate archive
     // download
     cy.route({
       method: 'GET',
       url: this.vaultFetchRevisionUrl,
       response: `fx:${this.revision}.gitfast.gz,binary`,
       headers: {
         'Content-disposition': `attachment; filename=${this.revision}.gitfast.gz`,
         'Content-Type': 'application/gzip'
       }
     }).as('fetchCookedArchive');
 
     cy.get(`#vault-task-${this.revision} .vault-dl-link button`)
       .click();
 
     cy.wait('@fetchCookedArchive').then((xhr) => {
       assert.isNotNull(xhr.response.body);
     });
   });
 
   it('should offer to recook an archive if no more available to download', function() {
 
     updateVaultItemList(this.Urls.browse_vault(), vaultItems);
 
     // Send 404 when fetching vault item
     cy.route({
       method: 'GET',
       status: 404,
       url: this.vaultFetchRevisionUrl,
       response: {
         'exception': 'NotFoundExc',
         'reason': `Revision with ID '${this.revision}' not found.`
       },
       headers: {
         'Content-Type': 'json'
       }
     }).as('fetchCookedArchive');
 
     cy.get(`#vault-task-${this.revision} .vault-dl-link button`)
       .click();
 
     cy.wait('@fetchCookedArchive').then(() => {
       cy.route({
         method: 'POST',
         url: this.vaultRevisionUrl,
         response: this.genVaultRevCookingResponse('new')
       }).as('createVaultCookingTask');
 
       cy.route({
         method: 'GET',
         url: this.vaultRevisionUrl,
         response: this.genVaultRevCookingResponse('new')
       }).as('checkVaultCookingTask');
 
       cy.get('#vault-recook-object-modal > .modal-dialog')
         .should('be.visible')
         .contains('button:visible', 'Ok')
         .click();
 
       cy.wait('@createVaultCookingTask')
         .wait('@checkVaultCookingTask')
         .then(() => {
           testStatus(this.revision, progressbarColors['new'], 'new', 'new');
         });
     });
   });
 
   it('should remove selected vault items', function() {
 
     updateVaultItemList(this.Urls.browse_vault(), vaultItems);
 
     cy.get(`#vault-task-${this.revision}`)
       .find('input[type="checkbox"]')
       .click();
     cy.contains('button', 'Remove selected tasks')
       .click();
 
     cy.get(`#vault-task-${this.revision}`)
       .should('not.exist');
   });
 
   it('should offer to immediately download a directory tarball if already cooked', function() {
 
     // Browse a directory
     cy.visit(this.directoryUrl);
 
     // Stub responses when requesting the vault API to simulate
     // the directory tarball has already been cooked
     cy.route({
       method: 'GET',
       url: this.vaultDirectoryUrl,
       response: this.genVaultDirCookingResponse('done')
     }).as('checkVaultCookingTask');
 
     // Stub response to the vault API to simulate archive download
     cy.route({
       method: 'GET',
       url: this.vaultFetchDirectoryUrl,
       response: `fx:${this.directory}.tar.gz,binary`,
       headers: {
         'Content-disposition': `attachment; filename=${this.directory}.tar.gz`,
         'Content-Type': 'application/gzip'
       }
     }).as('fetchCookedArchive');
 
     // Create a vault cooking task through the GUI
     checkVaultCookingTask('as tarball');
 
     // Start archive download through the GUI
     cy.get('.modal-dialog')
       .contains('button:visible', 'Ok')
       .click();
 
     cy.wait('@fetchCookedArchive');
 
   });
 
   it('should offer to immediately download a revision gitfast archive if already cooked', function() {
 
     // Browse a directory
     cy.visit(this.revisionUrl);
 
     // Stub responses when requesting the vault API to simulate
     // the directory tarball has already been cooked
     cy.route({
       method: 'GET',
       url: this.vaultRevisionUrl,
       response: this.genVaultRevCookingResponse('done')
     }).as('checkVaultCookingTask');
 
     // Stub response to the vault API to simulate archive download
     cy.route({
       method: 'GET',
       url: this.vaultFetchRevisionUrl,
       response: `fx:${this.revision}.gitfast.gz,binary`,
       headers: {
         'Content-disposition': `attachment; filename=${this.revision}.gitfast.gz`,
         'Content-Type': 'application/gzip'
       }
     }).as('fetchCookedArchive');
 
     checkVaultCookingTask('as git');
 
     // Start archive download through the GUI
     cy.get('.modal-dialog')
       .contains('button:visible', 'Ok')
       .click();
 
     cy.wait('@fetchCookedArchive');
 
   });
 
+  it('should offer to recook an object if previous vault task failed', function() {
+
+    cy.visit(this.directoryUrl);
+
+    // Stub responses when requesting the vault API to simulate
+    // the last cooking of the directory tarball has failed
+    cy.route({
+      method: 'GET',
+      url: this.vaultDirectoryUrl,
+      response: this.genVaultDirCookingResponse('failed')
+    }).as('checkVaultCookingTask');
+
+    checkVaultCookingTask('as tarball');
+
+    // Check that recooking the directory is offered to user
+    cy.get('.modal-dialog')
+      .contains('button:visible', 'Ok')
+      .should('be.visible');
+  });
+
 });
diff --git a/swh/web/assets/src/bundles/vault/vault-create-tasks.js b/swh/web/assets/src/bundles/vault/vault-create-tasks.js
index fc741677..8238dbc4 100644
--- a/swh/web/assets/src/bundles/vault/vault-create-tasks.js
+++ b/swh/web/assets/src/bundles/vault/vault-create-tasks.js
@@ -1,121 +1,125 @@
 /**
  * Copyright (C) 2018-2019  The Software Heritage developers
  * See the AUTHORS file at the top-level directory of this distribution
  * License: GNU Affero General Public License version 3, or any later version
  * See top-level LICENSE file for more information
  */
 
 import {handleFetchError, csrfPost} from 'utils/functions';
 
 export function vaultRequest(objectType, objectId) {
   let vaultUrl;
   if (objectType === 'directory') {
     vaultUrl = Urls.api_1_vault_cook_directory(objectId);
   } else {
     vaultUrl = Urls.api_1_vault_cook_revision_gitfast(objectId);
   }
   // check if object has already been cooked
   fetch(vaultUrl)
     .then(response => response.json())
     .then(data => {
       // object needs to be cooked
-      if (data.exception === 'NotFoundExc') {
+      if (data.exception === 'NotFoundExc' || data.status === 'failed') {
+        // if last cooking has failed, remove previous task info from localStorage
+        // in order to force the recooking of the object
+        swh.vault.removeCookingTaskInfo([objectId]);
         $(`#vault-cook-${objectType}-modal`).modal('show');
-      // object has been cooked and is in the vault cache
+      // object has been cooked and should be in the vault cache,
+      // it will be asked to cook it again if it is not
       } else if (data.status === 'done') {
         $(`#vault-fetch-${objectType}-modal`).modal('show');
       }
     });
 }
 
 function addVaultCookingTask(cookingTask) {
   let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
   if (!vaultCookingTasks) {
     vaultCookingTasks = [];
   }
   if (vaultCookingTasks.find(val => {
     return val.object_type === cookingTask.object_type &&
             val.object_id === cookingTask.object_id;
   }) === undefined) {
     let cookingUrl;
     if (cookingTask.object_type === 'directory') {
       cookingUrl = Urls.api_1_vault_cook_directory(cookingTask.object_id);
     } else {
       cookingUrl = Urls.api_1_vault_cook_revision_gitfast(cookingTask.object_id);
     }
     if (cookingTask.email) {
       cookingUrl += '?email=' + cookingTask.email;
     }
     csrfPost(cookingUrl)
       .then(handleFetchError)
       .then(() => {
         vaultCookingTasks.push(cookingTask);
         localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
         $('#vault-cook-directory-modal').modal('hide');
         $('#vault-cook-revision-modal').modal('hide');
         window.location = Urls.browse_vault();
       })
       .catch(() => {
         $('#vault-cook-directory-modal').modal('hide');
         $('#vault-cook-revision-modal').modal('hide');
       });
   } else {
     window.location = Urls.browse_vault();
   }
 }
 
 function validateEmail(email) {
   let re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
   return re.test(String(email).toLowerCase());
 }
 
 export function cookDirectoryArchive(directoryId) {
   let email = $('#swh-vault-directory-email').val().trim();
   if (!email || validateEmail(email)) {
     let cookingTask = {
       'object_type': 'directory',
       'object_id': directoryId,
       'email': email,
       'status': 'new'
     };
     addVaultCookingTask(cookingTask);
 
   } else {
     $('#invalid-email-modal').modal('show');
   }
 }
 
 export function fetchDirectoryArchive(directoryId) {
   $('#vault-fetch-directory-modal').modal('hide');
   const vaultUrl = Urls.api_1_vault_cook_directory(directoryId);
   fetch(vaultUrl)
     .then(response => response.json())
     .then(data => {
       swh.vault.fetchCookedObject(data.fetch_url);
     });
 }
 
 export function cookRevisionArchive(revisionId) {
   let email = $('#swh-vault-revision-email').val().trim();
   if (!email || validateEmail(email)) {
     let cookingTask = {
       'object_type': 'revision',
       'object_id': revisionId,
       'email': email,
       'status': 'new'
     };
     addVaultCookingTask(cookingTask);
   } else {
     $('#invalid-email-modal').modal('show');
   }
 }
 
 export function fetchRevisionArchive(revisionId) {
   $('#vault-fetch-directory-modal').modal('hide');
   const vaultUrl = Urls.api_1_vault_cook_revision_gitfast(revisionId);
   fetch(vaultUrl)
     .then(response => response.json())
     .then(data => {
       swh.vault.fetchCookedObject(data.fetch_url);
     });
 }
diff --git a/swh/web/assets/src/bundles/vault/vault-ui.js b/swh/web/assets/src/bundles/vault/vault-ui.js
index 213aab1c..0124d514 100644
--- a/swh/web/assets/src/bundles/vault/vault-ui.js
+++ b/swh/web/assets/src/bundles/vault/vault-ui.js
@@ -1,245 +1,252 @@
 /**
  * Copyright (C) 2018-2019  The Software Heritage developers
  * See the AUTHORS file at the top-level directory of this distribution
  * License: GNU Affero General Public License version 3, or any later version
  * See top-level LICENSE file for more information
  */
 
 import {handleFetchError, handleFetchErrors, csrfPost} from 'utils/functions';
 
 let progress = `<div class="progress">
                   <div class="progress-bar progress-bar-success progress-bar-striped"
                        role="progressbar" aria-valuenow="100" aria-valuemin="0"
                        aria-valuemax="100" style="width: 100%;height: 100%;">
                   </div>
                 </div>;`;
 
 let pollingInterval = 5000;
 let checkVaultId;
 
 function updateProgressBar(progressBar, cookingTask) {
   if (cookingTask.status === 'new') {
     progressBar.css('background-color', 'rgba(128, 128, 128, 0.5)');
   } else if (cookingTask.status === 'pending') {
     progressBar.css('background-color', 'rgba(0, 0, 255, 0.5)');
   } else if (cookingTask.status === 'done') {
     progressBar.css('background-color', '#5cb85c');
   } else if (cookingTask.status === 'failed') {
     progressBar.css('background-color', 'rgba(255, 0, 0, 0.5)');
     progressBar.css('background-image', 'none');
   }
   progressBar.text(cookingTask.progress_message || cookingTask.status);
   if (cookingTask.status === 'new' || cookingTask.status === 'pending') {
     progressBar.addClass('progress-bar-animated');
   } else {
     progressBar.removeClass('progress-bar-striped');
   }
 }
 
 let recookTask;
 
 // called when the user wants to download a cooked archive
 export function fetchCookedObject(fetchUrl) {
   recookTask = null;
   // first, check if the link is still available from the vault
   fetch(fetchUrl)
     .then(response => {
       // link is still alive, proceed to download
       if (response.ok) {
         $('#vault-fetch-iframe').attr('src', fetchUrl);
       // link is dead
       } else {
         // get the associated cooking task
         let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
         for (let i = 0; i < vaultCookingTasks.length; ++i) {
           if (vaultCookingTasks[i].fetch_url === fetchUrl) {
             recookTask = vaultCookingTasks[i];
             break;
           }
         }
         // display a modal asking the user if he wants to recook the archive
         $('#vault-recook-object-modal').modal('show');
       }
     });
 }
 
 // called when the user wants to recook an archive
 // for which the download link is not available anymore
 export function recookObject() {
   if (recookTask) {
     // stop cooking tasks status polling
     clearTimeout(checkVaultId);
     // build cook request url
     let cookingUrl;
     if (recookTask.object_type === 'directory') {
       cookingUrl = Urls.api_1_vault_cook_directory(recookTask.object_id);
     } else {
       cookingUrl = Urls.api_1_vault_cook_revision_gitfast(recookTask.object_id);
     }
     if (recookTask.email) {
       cookingUrl += '?email=' + recookTask.email;
     }
     // request archive cooking
     csrfPost(cookingUrl)
       .then(handleFetchError)
       .then(() => {
         // update task status
         recookTask.status = 'new';
         let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
         for (let i = 0; i < vaultCookingTasks.length; ++i) {
           if (vaultCookingTasks[i].object_id === recookTask.object_id) {
             vaultCookingTasks[i] = recookTask;
             break;
           }
         }
         // save updated tasks to local storage
         localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
         // restart cooking tasks status polling
         checkVaultCookingTasks();
         // hide recook archive modal
         $('#vault-recook-object-modal').modal('hide');
       })
       // something went wrong
       .catch(() => {
         checkVaultCookingTasks();
         $('#vault-recook-object-modal').modal('hide');
       });
   }
 }
 
 function checkVaultCookingTasks() {
   let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
   if (!vaultCookingTasks || vaultCookingTasks.length === 0) {
     $('.swh-vault-table tbody tr').remove();
     checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval);
     return;
   }
   let cookingTaskRequests = [];
   let tasks = {};
   let currentObjectIds = [];
 
   for (let i = 0; i < vaultCookingTasks.length; ++i) {
     let cookingTask = vaultCookingTasks[i];
     currentObjectIds.push(cookingTask.object_id);
     tasks[cookingTask.object_id] = cookingTask;
     let cookingUrl;
     if (cookingTask.object_type === 'directory') {
       cookingUrl = Urls.api_1_vault_cook_directory(cookingTask.object_id);
     } else {
       cookingUrl = Urls.api_1_vault_cook_revision_gitfast(cookingTask.object_id);
     }
     if (cookingTask.status !== 'done' && cookingTask.status !== 'failed') {
       cookingTaskRequests.push(fetch(cookingUrl));
     }
   }
   $('.swh-vault-table tbody tr').each((i, row) => {
     let objectId = $(row).find('.vault-object-id').data('object-id');
     if ($.inArray(objectId, currentObjectIds) === -1) {
       $(row).remove();
     }
   });
   Promise.all(cookingTaskRequests)
     .then(handleFetchErrors)
     .then(responses => Promise.all(responses.map(r => r.json())))
     .then(cookingTasks => {
       let table = $('#vault-cooking-tasks tbody');
       for (let i = 0; i < cookingTasks.length; ++i) {
         let cookingTask = tasks[cookingTasks[i].obj_id];
         cookingTask.status = cookingTasks[i].status;
         cookingTask.fetch_url = cookingTasks[i].fetch_url;
         cookingTask.progress_message = cookingTasks[i].progress_message;
       }
       for (let i = 0; i < vaultCookingTasks.length; ++i) {
         let cookingTask = vaultCookingTasks[i];
 
         let rowTask = $('#vault-task-' + cookingTask.object_id);
 
         let downloadLinkWait = 'Waiting for download link to be available';
         if (!rowTask.length) {
 
           let browseUrl;
           if (cookingTask.object_type === 'directory') {
             browseUrl = Urls.browse_directory(cookingTask.object_id);
           } else {
             browseUrl = Urls.browse_revision(cookingTask.object_id);
           }
 
           let progressBar = $.parseHTML(progress)[0];
           let progressBarContent = $(progressBar).find('.progress-bar');
           updateProgressBar(progressBarContent, cookingTask);
           let tableRow;
           if (cookingTask.object_type === 'directory') {
             tableRow = `<tr id="vault-task-${cookingTask.object_id}" title="Once downloaded, the directory can be extracted with the ` +
                        `following command:\n\n$ tar xvzf ${cookingTask.object_id}.tar.gz">`;
           } else {
             tableRow = `<tr id="vault-task-${cookingTask.object_id}"  title="Once downloaded, the git repository can be imported with the ` +
                        `following commands:\n\n$ git init\n$ zcat ${cookingTask.object_id}.gitfast.gz | git fast-import">`;
           }
           tableRow += '<td><input type="checkbox" class="vault-task-toggle-selection"/></td>';
           tableRow += `<td style="width: 120px"><i class="${swh.webapp.getSwhObjectIcon(cookingTask.object_type)} fa-fw"></i>${cookingTask.object_type}</td>`;
           tableRow += `<td class="vault-object-id" data-object-id="${cookingTask.object_id}"><a href="${browseUrl}">${cookingTask.object_id}</a></td>`;
           tableRow += `<td style="width: 350px">${progressBar.outerHTML}</td>`;
           let downloadLink = downloadLinkWait;
           if (cookingTask.status === 'done') {
             downloadLink = `<button class="btn btn-default btn-sm" onclick="swh.vault.fetchCookedObject('${cookingTask.fetch_url}')` +
                            '"><i class="fa fa-download fa-fw" aria-hidden="true"></i>Download</button>';
           } else if (cookingTask.status === 'failed') {
             downloadLink = '';
           }
           tableRow += `<td class="vault-dl-link" style="width: 320px">${downloadLink}</td>`;
           tableRow += '</tr>';
           table.prepend(tableRow);
         } else {
           let progressBar = rowTask.find('.progress-bar');
           updateProgressBar(progressBar, cookingTask);
           let downloadLink = rowTask.find('.vault-dl-link');
           if (cookingTask.status === 'done') {
             downloadLink[0].innerHTML = `<button class="btn btn-default btn-sm" onclick="swh.vault.fetchCookedObject('${cookingTask.fetch_url}')` +
                                         '"><i class="fa fa-download fa-fw" aria-hidden="true"></i>Download</button>';
           } else if (cookingTask.status === 'failed') {
             downloadLink[0].innerHTML = '';
           } else if (cookingTask.status === 'new') {
             downloadLink[0].innerHTML = downloadLinkWait;
           }
         }
       }
       localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
       checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval);
     })
     .catch(() => {});
 }
 
+export function removeCookingTaskInfo(tasksToRemove) {
+  let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
+  if (!vaultCookingTasks) {
+    return;
+  }
+  vaultCookingTasks = $.grep(vaultCookingTasks, task => {
+    return $.inArray(task.object_id, tasksToRemove) === -1;
+  });
+  localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
+}
+
 export function initUi() {
 
   $('#vault-tasks-toggle-selection').change(event => {
     $('.vault-task-toggle-selection').prop('checked', event.currentTarget.checked);
   });
 
   $('#vault-remove-tasks').click(() => {
     clearTimeout(checkVaultId);
     let tasksToRemove = [];
     $('.swh-vault-table tbody tr').each((i, row) => {
       let taskSelected = $(row).find('.vault-task-toggle-selection').prop('checked');
       if (taskSelected) {
         let objectId = $(row).find('.vault-object-id').data('object-id');
         tasksToRemove.push(objectId);
         $(row).remove();
       }
     });
-    let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
-    vaultCookingTasks = $.grep(vaultCookingTasks, task => {
-      return $.inArray(task.object_id, tasksToRemove) === -1;
-    });
-    localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
+    removeCookingTaskInfo(tasksToRemove);
     $('#vault-tasks-toggle-selection').prop('checked', false);
     checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval);
   });
 
   checkVaultCookingTasks();
 
   window.onfocus = () => {
     clearTimeout(checkVaultId);
     checkVaultCookingTasks();
   };
 
 }